list[...]
использует индексирование или более точный подписка синтаксис , а [start:stop:step]
представляет собой срез .В таких случаях Python 3 передает объект slice()
в вызов подписки, в исходном коде CPython нет ::
, поскольку синтаксический анализатор языка не может угрожать этой части.Нотация нарезки допускает значения по умолчанию, пустой слот между частями start:stop:step
означает, что выбрано значение по умолчанию, а list[::-1]
оставляет значения по умолчанию start
и stop
(представлены None
, и значение шага установленона -1
.
Все это просто означает, что вам нужно запомнить, чтобы отделял синтаксис от операций . Вы можете анализировать синтаксис, анализируя его в абстрактном синтаксическом дереве, или разбирая байт-кодсгенерированный для него. Когда вы создадите абстрактное синтаксическое дерево для list[::-1]
, вы увидите, что синтаксический анализатор отделяет часть слайса:
>>> import ast
>>> ast.dump(ast.parse('list[::-1]', '', 'eval').body) # expression body only
"Subscript(value=Name(id='list', ctx=Load()), slice=Slice(lower=None, upper=None, step=UnaryOp(op=USub(), operand=Num(n=1))), ctx=Load())"
Итак, синтаксический узел Subscript()
работает с именем list
, передавая срез.
Создание разборки для этой операции отображается используемый байт-код:
>>> dis.dis('list[::-1]')
1 0 LOAD_NAME 0 (list)
2 LOAD_CONST 0 (None)
4 LOAD_CONST 0 (None)
6 LOAD_CONST 1 (-1)
8 BUILD_SLICE 3
10 BINARY_SUBSCR
12 RETURN_VALUE
Здесь BUILD_SLICE()
операция с байт-кодом берет три верхних элемента из стека (помещенные туда с помощью LOAD_CONST
операций с индексами 2, 4 и 6) для создания объекта slice()
, который затем передается в list
объект (следующий настек) с BINARY_SUBSCR
.Последний байт-код является операцией Subscript()
в AST, а байт-коды 2-8 являются объектом Slice()
в AST.
Имея байт-код в руке, вы можете перейти к Цикл оценки байт-кода Python , чтобы увидеть, что эти байт-коды на самом деле делают .Я пропущу BUILD_SLICE
, это просто создание простого объекта slice()
для хранения значений start
, stop
и step
.
Сосредоточившись на коде операции BINARY_SUBSCR
, мы находим:
TARGET(BINARY_SUBSCR) {
PyObject *sub = POP();
PyObject *container = TOP();
PyObject *res = PyObject_GetItem(container, sub);
Py_DECREF(container);
Py_DECREF(sub);
SET_TOP(res);
if (res == NULL)
goto error;
DISPATCH();
}
Итак, Python берет вершину стека и помещает ее в sub
(здесь это объект slice()
), помещает ссылку list
в container
и затем вызывает PyObject_GetItem(container, sub)
.Вот и все.Следующий шаг - найти PyObject_GetItem()
.Это определено в abstract.c
, и первое, что он делает, это проверяет, является ли объект типом отображения:
m = o->ob_type->tp_as_mapping;
if (m && m->mp_subscript) {
PyObject *item = m->mp_subscript(o, key);
assert((item != NULL) ^ (PyErr_Occurred() != NULL));
return item;
}
->ob_type
является классом объекта (type(object)
делаетто же самое), и ->tp_as_mapping
устанавливается, когда поддерживается протокол сопоставления .Если протокол отображения поддерживается, то m->mp_subscript(o, key);
вызывается с o
, являющимся объектом списка, и m
, являющимся определением поддержки отображения списка.
Списки поддерживают этот протокол, потому что только этот протокол поддерживает объекты срезов(он также реализует протокол последовательности , но он принимает только целые числа и гораздо более старую, более ограниченную форму среза с двумя индексами).Мы можем видеть, что list
реализует протокол, ища запись tp_as_mapping
в определении типа PyTypeObject PyList_Type
:
PyTypeObject PyList_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"list",
sizeof(PyListObject),
0,
// ...
&list_as_mapping, /* tp_as_mapping */
// ...
};
, которая указывает на list_as_mapping
, который определяется как
static PyMappingMethods list_as_mapping = {
(lenfunc)list_length,
(binaryfunc)list_subscript,
(objobjargproc)list_ass_subscript
};
Итак, теперь мы знаем, где найти фактическую реализацию для операции индексирования;эта реализация обрабатывается функцией list_subscript()
, которая использует PySlice_Check()
для проверки slice()
объектов, а затем просто создает новый объект списка и копирует индексы:
if (slicelength <= 0) {
return PyList_New(0);
}
else if (step == 1) {
return list_slice(self, start, stop);
}
else {
result = list_new_prealloc(slicelength);
if (!result) return NULL;
src = self->ob_item;
dest = ((PyListObject *)result)->ob_item;
for (cur = start, i = 0; i < slicelength;
cur += (size_t)step, i++) {
it = src[cur];
Py_INCREF(it);
dest[i] = it;
}
Py_SIZE(result) = slicelength;
return result;
}
Для непустого слайса с размером шага -1
берется ветвь else
, где list_new_prealloc()
создает новый, предварительно выделенный список для размещения всех индексов слайсазатем цикл for
, использующий cur += (size_t)step
, используется для генерации индекса для копирования в новый список.
Для вашего среза list[::-1]
объект list
передается в объекте slice(None, None, -1)
, а list_subscript
использует функции PySlice_Unpack()
и PySlice_AdjustIndices()
чтобы превратить это в конкретные значения slicelength
, start
, stop
и step
;для отрицательных значений step
и start
и stop
, равных None
, конечным результатом является то, что slicelength
установлено на len(list)
, start
установлено на len(list) - 1
и stop
на-1
.
Таким образом, эффект в вышеупомянутом цикле for
заключается в том, что все элементы, начиная с индекса len(list) - 1
и повторяя его до индекса 0
, копируются в новый объект списка.
Таким образом, обращение с list[::-1]
является операцией O (N) для длины N списка (это включает предварительное выделение, которое занимает до O (N) времени, чтобы выделить память для ссылок на список нового списка ).