TL; DR любой объект, который реализует __call__
можно назвать например: функции, пользовательские классы, и т.д ..
Чуть более длинная версия: (ш все о е т доб)
Полный ответ на ваш вопрос о том, какая разница находится в реализации виртуальной машины python, поэтому мы должны взглянуть на python под капотом. Сначала идет концепция объекта кода. Python анализирует все, что вы бросаете на него, на свой собственный внутренний язык, который является одним и тем же на всех платформах, известных как байт-код. Очень визуальная репрезентация этого - когда вы получаете .pyc-файл после импорта пользовательской библиотеки, которую вы написали. Это необработанные инструкции для виртуальной машины python. Игнорируя, как эти инструкции создаются из вашего исходного кода, они затем выполняются PyEval_EvalFrameEx
в Python/ceval.c. Исходный код немного зверя, но в конечном счете работает как простой процессор с некоторыми сложными битами, отвлеченными. Байт-код - это язык ассемблера для этого процессора. В частности, один из «опкодов» для этого «процессора» (метко назван) CALL_FUNCTION
. Обратный вызов проходит через несколько вызовов, в конечном итоге доходя до PyObject_Call()
. Эта функция принимает указатель на PyObject
и извлекает атрибут tp_call
от его типа и непосредственно называет его (технически это проверяет, если он там первый):
...
call = func->ob_type->tp_call //func is an arg of PyObject_Call() and is a pointer to a PyObject
...
result = (*call)(func, arg, kw);
Любого объект, который реализует __call__
получает атрибут tp_call
с указателем к фактической функции. Я считаю, что обрабатывается slotdefs[]
Difinition от объектов/typeobject.c:
FLSLOT("__call__", tp_call, slot_tp_call, (wrapperfunc)wrap_call,
"__call__($self, /, *args, **kwargs)\n--\n\nCall self as a function.",
PyWrapperFlag_KEYWORDS)
__call__
метод сам по себе для функций определяется в реализации CPython и определяет, как питон VM должен начать выполнение байт-код для эту функцию и как данные должны быть возвращены (если они есть). Когда вы даете произвольный класс методу __call__
, атрибут представляет собой объект функции, который снова ссылается на реализацию cpython __call__
. Поэтому, когда вы вызываете «нормальную» функцию, ссылается foo.__call__
. когда вы вызываете класс, подлежащий вызову, self.__call__
эквивалентен foo
, а фактическое ссылочное имя cpython - self.__call__.im_func.__call__
.
отказ от ответственности
Это было путешествие в несколько неизведанные воды для меня, и это вполне возможно, я исказил некоторые тонкости реализации. В основном я взял от this сообщение в блоге о том, как работают python callables под капотом, а некоторые мои собственные копания через питон source code
Что вы подразумеваете под «простыми функциями»? –
Просьба привести пример, какой объект функции и функция, на ваш взгляд. –
Это похоже на вопрос, в чем разница между * plain 'int' * и *' int' объектом * в Python ... там * нет простого 'int' *. –