Функции, вызываемые объекты и как они созданы в Python

Мне интересно узнать о более сложных отличиях между функциями и вызываемыми объектами. Например, если вы выполните:

def foo1():
   return 2 + 4

class foo2:
   def __call__():
      return 2 + 4

import sys 
sys.getsizeof(foo1)  # returns 136
sys.getsizeof(foo2)  # returns 1016

Очевидно, что существует большая разница между функциями и вызываемыми объектами. Однако я не могу найти много документации о том, что происходит за кулисами. Я знаю, что функции являются первоклассными объектами, но я также знаю, что классы имеют гораздо больше возможностей, чем ваши обычные функции. класс foo2 создается с помощью метакласса type().

Теперь мои вопросы:

  • Когда вы создаете функцию def foo1():, как это отличается от процесс определения класса с метаклассом? Существует ли версия type(), но для функций - метафункция?
  • Скажите, что кто-то хотел написать свой собственный метафунд (реальная причина этого), было бы лучше просто использовать декораторов или, может быть, метакласс, который делает вызываемые классы? Какие преимущества будут предлагать (метакласс, который делает вызываемые классы неуклюжими)?
  • Единственная цель, заключающаяся в том, что вызываемый объект имеет функцию, которая также может хранить информацию в ней?

Ответы

Ответ 1

Функции также являются вызываемыми объектами:

>>> foo1.__call__
<method-wrapper '__call__' of function object at 0x105bafd90>
>>> callable(foo1)
True

Но класс должен отслеживать больше информации; здесь не имеет значения, что вы дали ему метод __call__. Любой класс больше, чем функция:

>>> import sys
>>> def foo1():
...    return 2 + 4
...
>>> class foo3:
...    pass
...
>>> sys.getsizeof(foo1)
136
>>> sys.getsizeof(foo3)
1056

Функциональный объект - это отдельный тип объекта:

>>> type(foo1)
<class 'function'>

и является достаточно компактным, потому что мясо фактически не находится в объекте функции, а в других объектах, на которые ссылается объект функции:

>>> sys.getsizeof(foo1.__code__)
144
>>> sys.getsizeof(foo1.__dict__)
240

И это действительно так; разные типы объектов имеют разные размеры, потому что они отслеживают разные вещи или используют композицию для хранения материала в других объектах.

Вы можете использовать возвращаемое значение type(foo1) (или types.FunctionType, которое является одним и тем же объектом) для создания новых объектов функции, если вы этого желаете:

>>> import types
>>> types.FunctionType(foo1.__code__, globals(), 'somename')
<function foo1 at 0x105fbc510>

что в основном делает интерпретатор всякий раз, когда выполняется оператор def function(..): ....

Используйте __call__, чтобы пользовательские классы вызывались, когда это имеет смысл для вашего API. Класс enum.Enum() может быть вызван, например, особенно потому, что использование синтаксиса вызова дает вам синтаксис, отличный от подписки, который использовался для других целей. А xmlrpc.client.ServerProxy() объект создает объекты метода, которые являются экземплярами _Method, поскольку они проксируют удаленный вызов, а не локальную функцию.

Ответ 2

Есть ли версия типа(), но для функций, метафункция?

Сорт. Функции имеют тип, и этот тип может использоваться для построения новых функций, как минимум, из кодового объекта и глобального словаря. (Объект кода можно построить с помощью compile() или захватить из существующей функции или, если вы мазохист, построенный из строки байт-кода и другой информации с помощью конструктора типа кода.) Тип функции не является мета- ничего, потому что функции - это экземпляры, а не классы. Вы можете получить этот тип с помощью type(lambda:0) (поместить любую функцию в круглые скобки) и сделать help(type(lambda:0)), чтобы увидеть его аргументы. Тип функции - это экземпляр type.

Скажите, что кто-то хотел написать свой собственный метафорум

Вы не можете подклассифицировать тип функции, извините.

class FunkyFunc(type(lambda: 0)): pass

TypeError: Error when calling the metaclass bases
    type 'function' is not an acceptable base type

Единственная цель, заключающаяся в том, что вызываемый объект имеет функцию, которая также может хранить информацию в ней?

Существует много применений. Ваш пример - один (хотя экземпляры функций могут иметь атрибуты, класс обеспечивает лучшую документацию по атрибутам); обратная сторона, делая обычный старый объект данных вызываемым, является другим (я дал своего рода напуганный пример этого в этом ответе). Вы можете использовать классы для написания декораторов, если экземпляры вызываются. Вы можете использовать __call__ в метаклассе для настройки конструкции экземпляра. И вы можете использовать его для написания функций с различным поведением, вроде как если бы вы могли подклассифицировать тип функции. (Если вы хотите использовать "статические" переменные C-стиля в функции, вы можете записать ее как класс с помощью метода __call__ и использовать атрибуты для хранения статических данных.)

Интересная вещь о объектах функций заключается в том, что, поскольку они вызываемы, они сами имеют метод __call__. Но метод может быть вызван, и поэтому метод __call__ также имеет метод __call__ и что метод __call__ (вызываемый) также имеет метод __call__ и т.д. До бесконечности.: -)