Как найти реализацию [:: - 1] (список обращений в Python) в исходном коде CPython - PullRequest
0 голосов
/ 12 сентября 2018

Я пытался перевернуть список в Python.Существует много методов, и [::-1] кажется отличным!Но мне любопытно, как это делается [::-1]?Какова временная сложность этого?

Я искал репозиторий CPython в github, но не смог найти никакой подсказки.Моя стратегия поиска заключалась в том, что я искал ключевое слово: :: в репозитории CPython.Поскольку в синтаксисе python есть ::, то есть [::-1], в исходном коде CPython может быть ::, так что [::-1] может ссылаться на него и выполнять обратную операцию.Имеет ли это смысл?

1 Ответ

0 голосов
/ 12 сентября 2018

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) времени, чтобы выделить память для ссылок на список нового списка ).

...