Что более фундаментально: функции Python или объектные методы Python?
Я пытаюсь получить концептуальное представление о природе функций и методов Python. Я получаю, что функции фактически являются объектами, с методом, который вызывается при выполнении функции. Но является ли этот метод объекта-объекта фактически другой функцией?
Например:
def fred():
pass
Если я смотрю на dir(fred)
, я вижу, что у него есть атрибут с именем __call__
. Но dir(fred.__call__)
также имеет атрибут с именем __call__
. Так что fred.__call__.__call__
и так далее. Иды этой цепочки объектов __call__
предполагают, что они все различны. Действительно ли они объективны или это какой-то трюк низкого уровня интерпретатора?
Что более важно: функции или объектные методы?
Ответы
Ответ 1
Что более фундаментально: функции или объектных методов?
Я думаю, лучший ответ может быть "ни". См. "Модель выполнения" в ссылке на Python, где она относится к "блокам". Это то, что фактически выполняется. Вещь __call__
, которую вы повесили в бесконечном поиске конца, - это просто оболочка, которая знает, как выполнить блок кода (см. Различные атрибуты func_xxx
вашей функции, а фактический байт-код будет сохранен как func_code
).
Также уместно, Определения функций, который ссылается на "объект функции [являющийся] (обертка вокруг исполняемого кода для функция)". Наконец, существует термин вызываемый, который также может быть ответом на "что более фундаментально?"
Ответ 2
Короткий ответ: оба являются фундаментальными, .__call__()
для функций - всего лишь виртуальный трюк.
Остальная часть этого ответа немного сложна. Вы не должны это понимать, но я нахожу тему интересной. Будьте предупреждены, что я представлю серию лжи, постепенно их исправляя.
Длинный ответ
На самом фундаментальном уровне можно сказать, что Python имеет всего 2 операции:
- доступ к атрибутам:
obj.attr
- вызов функции:
callable(args)
Вызов метода - obj.method(args)
- не является фундаментальным. Они состоят из двух шагов: выбор атрибута obj.method
(который дает объект вызываемого объекта "связанный метод" ) и вызов этого с помощью args
.
Другие операторы определяются в терминах них. Например. x + y
пытается x.__add__(y)
, возвращаясь к другим подобным комбинациям, если это не работает.
Бесконечно длинный ответ?
Пока все хорошо. Но вызов и атрибут доступа также определяются в терминах obj.__call__(args)
и obj.__getattribute__(name)
?!?
Это черепахи полностью вниз?!?
Фокус в том, что операции над объектом определяются вызывающими методами его типа: type(obj).__call__(obj, args)
и type(obj).__getattribute__(obj, name)
. Какой BTW означает, что я лгал вам, и есть третья фундаментальная операция:
- получение типа объекта:
type(obj)
ОК, это все еще не помогает. type(obj).__call__(...)
все еще включает в себя доступ к атрибуту и вызов, поэтому это должно продолжаться до бесконечности? В результате вы попадаете в встроенный тип - обычно это функция, object
или type
- и для них основные функции имеют доступ к атрибутам и функциям.
Поэтому, когда вы вызываете экземпляр пользовательского класса, который реализован через его метод __call__
. Но его метод __call__
, вероятно, является нормальной функцией, которая может быть вызвана непосредственно. Конец тайны.
Аналогично о __getattribute__
- вы можете предоставить ему определение доступа к атрибуту для вашего класса, но сам класс реализует доступ к атрибутам принципиально (если у него нет пользовательского метакласса).
Занавес перед человеком
Итак, почему даже функция имеет метод fred.__call__
? Ну, это просто дым и зеркала, которые Python тянет, чтобы размыть разницу между встроенными типами и пользовательскими классами. Этот метод существует для всех вызываемых объектов, но вызов нормальной функции не должен проходить через него - функции в принципе могут быть вызваны.
Аналогично, все объекты имеют obj.__getattribute__
и obj.__class__
, но для встроенных типов он просто предоставляет фундаментальные операции вместо определения.
Малая печать
Первое утверждение о том, что Python имеет 2 фундаментальных операции, было фактически полной ложью. Технически все операторы Python имеют "фундаментальную" операцию на уровне C, выставленную для согласованности с помощью метода, а пользовательские классы могут переопределять эти операции с помощью аналогичных методов.
Но рассказ, который я вам сказал, может быть правдой, и это сводит вопрос к его центру: почему .__call__()
и .__getattribute__()
не являются бесконечной рекурсией.
Ответ 3
Не только ответ на Python, но на самом низком уровне процессор понимает только действия и переменные. Из этого мы экстраполируем функции, а из переменных и функций экстраполируем объекты. Поэтому с точки зрения программирования на низком уровне я бы сказал, что более фундаментальной является функция.
Это не обязательно верно для Python в питоническом смысле и, вероятно, является хорошим примером того, почему не всегда полезно глубже вникать в реализацию языка в качестве пользователя.:) Думая о функции как объекте, безусловно, лучший ответ в самом Python.
Сначала я думал, что ваши звонки отслеживаются в библиотеке Python, но метод .call имеет те же свойства, что и любой другой метод. Таким образом, он рекурсивно исследует себя, я думаю, играл с CLI python в течение нескольких минут; Я думаю, что это болезненный способ изучения архитектуры и, хотя и не обязательно ошибка, как свойство Python обрабатывает объекты под обложками.:)