PyList_Reverse
является частью C -API, вы бы назвали его, если бы манипулировали списками Python в C, он не используется ни в одном из двух случаев.
Они оба go - list_reverse_impl
(на самом деле list_reverse
, который охватывает list_reverse_impl
), что является функцией C, которая реализует как list.reverse
, так и list_instance.reverse
.
Оба вызова обрабатываются call_function
в ceval
, после чего создается сгенерированный для них код операции CALL_METHOD
(операторы dis.dis
видят его). call_function
претерпел значительные изменения в Python 3.8 (с введением PEP 590 ), так что то, что происходит оттуда, вероятно, слишком велико для предмета, чтобы в него можно было ответить в одном вопросе .
Дополнительные вопросы:
Как сходятся два пути из двух выражений python? Если я правильно понимаю, разбор и обсуждение байт-кода и того, что происходит со стеком, особенно LOAD_METHOD
, прояснит это.
Начнем после того, как оба выражения скомпилированы в соответствующие представления байт-кода:
l = [1, 2, 3, 4]
Случай A, для l.reverse()
имеем:
1 0 LOAD_NAME 0 (l)
2 LOAD_METHOD 1 (reverse)
4 CALL_METHOD 0
6 RETURN_VALUE
Случай B, для list.reverse(l)
у нас есть:
1 0 LOAD_NAME 0 (list)
2 LOAD_METHOD 1 (reverse)
4 LOAD_NAME 2 (l)
6 CALL_METHOD 1
8 RETURN_VALUE
Мы можем смело игнорировать код операции RETURN_VALUE
, здесь не имеет значения
Давайте сосредоточимся на отдельных реализациях для каждого кода операции, а именно LOAD_NAME
, LOAD_METHOD
и CALL_METHOD
. Мы можем видеть, что помещается в стек значений , просматривая, какие операции вызываются для него. (Обратите внимание, что оно инициализируется, чтобы указывать на стек значений, расположенный внутри объекта кадра для каждого выражения.)
LOAD_NAME
:
Что выполняется в этом случае это довольно просто. Учитывая наше имя, l
или list
в каждом случае (каждое имя находится в `co-> co_names, кортеже, в котором хранятся имена, которые мы используем внутри объекта кода), шаги:
- Ищите имя внутри
locals
. Если найдено, go до 4. - Найдите имя внутри
globals
. Если найдено, go до 4. - Найдите имя внутри
builtins
. Если найдено, go до 4. - Если найдено, значение pu sh, обозначаемое именем в стеке. Иначе, NameError.
В случае A имя l
встречается в глобальных переменных. В случае B он находится во встроенных файлах. Итак, после LOAD_NAME
стек выглядит следующим образом:
Дело A: stack_pointer -> [1, 2, 3, 4]
Дело B: stack_pointer -> <type list>
LOAD_METHOD
:
Во-первых, мне не следует, чтобы этот код операции генерировался, только если выполняется доступ к атрибуту (т. Е. obj.attr
). Вы также можете получить метод и вызвать его через a = obj.attr
, а затем a()
, но это приведет к генерации кода операции CALL_FUNCTION
(подробнее см. Далее).
После загрузки имени При вызове (reverse
в обоих случаях) мы ищем объект в верхней части стека (либо [1, 2, 3, 4]
, либо list
) для метода с именем reverse
. Это делается с помощью _PyObject_GetMethod
, его документация гласит:
Возвращает 1, если метод найден, 0, если это обычный атрибут из __dict__
или что-то, возвращаемое с помощью протокол дескриптора.
Метод встречается только в случае A, когда мы обращаемся к атрибуту (reverse
) через экземпляр объекта списка. В случае B вызываемый объект возвращается после вызова протокола дескриптора, поэтому возвращаемое значение равно 0 (но мы, конечно, возвращаем объект!).
Здесь мы расходимся с возвращаемым значением:
В случае A:
SET_TOP(meth);
PUSH(obj); // self
У нас есть SET_TOP
, за которым следует PUSH
. Мы переместили метод на вершину стека, а затем снова набрали значение sh. В этом случае stack_pointer
теперь выглядит:
stack_pointer -> [1, 2, 3, 4]
<reverse method of lists>
В случае B мы имеем:
SET_TOP(NULL);
Py_DECREF(obj);
PUSH(meth);
Снова SET_TOP
, за которым следует PUSH
. Счетчик ссылок obj
(т.е. list
) уменьшен, потому что, насколько я могу судить, он больше не нужен. В этом случае стек теперь выглядит так:
stack_pointer -> <reverse method of lists>
NULL
Для случая B у нас есть дополнительный LOAD_NAME
. Следуя предыдущим шагам, стек для дела B теперь становится:
stack_pointer -> [1, 2, 3, 4]
<reverse method of lists>
NULL
Довольно похоже.
CALL_METHOD
:
Это не ' не вносить никаких изменений в стек. В обоих случаях вызывается call_function
с передачей состояния потока, указателя стека и количества позиционных аргументов (oparg
).
Единственное отличие состоит в выражении, используемом для передачи позиционных аргументов.
Для случая A нам нужно учесть неявный self
, который должен быть вставлен в качестве первого позиционного аргумента. Поскольку сгенерированный для него код операции не сигнализирует о том, что позиционный аргумент был передан (поскольку ни один из них не был передан явно):
4 CALL_METHOD 0
мы вызываем call_function
с oparg + 1 = 0 + 1 = 1
, чтобы сигнализировать об этом одном позиционном Аргумент существует в стеке ([1, 2, 3, 4
]).
В случае B, где мы явно передаем экземпляр в качестве первого аргумента, это учитывается:
6 CALL_METHOD 1
, поэтому вызов call_function
может сразу передать oparg
в качестве значение для позиционных аргументов.
Что такое «несвязанный метод», помещенный в стек? Это «C функция» (какая?) Или «Python объект»?
Это Python объект, который оборачивается вокруг C функции. Объект Python является дескриптором метода, а функция C, которую он переносит, - list_reverse
.
Все встроенные методы и функции реализованы в C. Во время инициализации CPython инициализирует все встроенные функции (см. list
здесь ) и добавляет оболочки для всех методов . Эти оболочки (объекты) являются дескрипторами, которые используются для реализации Методы и Функции .
Когда метод извлекается из класса через один из его экземпляров, он считается связанным с этим экземпляром. Это можно увидеть, посмотрев на присвоенный ему атрибут __self__
:
m = [1, 2, 3, 4].reverse
m() # use __self__
print(m.__self__) # [4, 3, 2, 1]
Этот метод все еще можно вызывать даже без экземпляра, который его квалифицирует. Это связано с этим экземпляром. (ПРИМЕЧАНИЕ. Это обрабатывается кодом операции CALL_FUNCTION
, а не LOAD/CALL_METHOD
).
Несвязанный метод - это метод, который еще не связан с экземпляром. list.reverse
не связан, он ожидает вызова через экземпляр для привязки к нему.
То, что не связано, не означает, что его нельзя вызвать, list.reverse
вызывается просто отлично, если вы явно передаете аргумент self
в качестве аргумента. Помните, что методы - это просто специальные функции, которые (помимо прочего) неявно передают self
в качестве первого аргумента после привязки к экземпляру.
Как я могу сказать, что это функция list_reverse
в файл listobject. c .h?
Это просто, вы можете увидеть, как методы списка инициализируются в listobject.c
. LIST_REVERSE_METHODDEF
- это просто макрос, который после замены добавляет функцию list_reverse
в этот список. tp_methods
списка затем заключаются в объекты функций, как указано ранее.
Здесь все может показаться сложным, потому что CPython использует внутренний инструмент, аргумент clini c, для автоматизации обработки аргументов. Это как бы двигает определения, слегка запутывая.