Funciones, objetos callables y cómo se crean ambos en Python

Me pregunto sobre las diferencias más complejas entre las funciones y los objetos que se pueden llamar. Por ejemplo, si lo hace:

def foo1(): return 2 + 4 class foo2: def __call__(): return 2 + 4 import sys sys.getsizeof(foo1) # returns 136 sys.getsizeof(foo2) # returns 1016 

Hay una clara diferencia entre las funciones y los objetos que se pueden llamar. Sin embargo, no puedo encontrar mucha documentación sobre lo que está sucediendo entre bastidores. Sé que las funciones son objetos de primera clase, pero también sé que las clases tienen muchas más actividades que las funciones normales. La clase foo2 se crea con una metaclase, type() .

Mis preguntas entonces, son estas:

  1. Cuando creas una función def foo1(): ¿cómo difiere esto del proceso de definir una clase con una metaclase? ¿Existe una versión de type() pero para funciones, una metafunción?
  2. Digamos que alguien quería escribir su propia metafunción (la verdadera razón detrás de esto), ¿sería mejor usar solo decoradores, o tal vez una metaclase que hace clases que se pueden llamar? ¿Qué ventajas ofrecería cualquiera de ellas (una metaclase que hace que las clases callamables parezcan torpes)?
  3. ¿Es el único propósito detrás de tener un objeto llamable tener una función que también pueda almacenar información en él?

Las funciones también son objetos que se pueden llamar:

 >>> foo1.__call__  >>> callable(foo1) True 

Pero una clase necesita hacer un seguimiento de más información ; No importa aquí que le hayas dado un método __call__ . Cualquier clase es más grande que una función:

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

Un objeto de función es un tipo de objeto distinto:

 >>> type(foo1)  

y es razonablemente compacto porque la carne no está realmente en el objeto de función sino en otros objetos referenciados por el objeto de función:

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

Y eso es todo; diferentes tipos de objetos tienen diferentes tamaños porque rastrean diferentes cosas o usan la composición para almacenar cosas en otros objetos.

Puede usar el valor de retorno de type(foo1) (o types.FunctionType , que es el mismo objeto) para producir nuevos objetos de función si así lo desea:

 >>> import types >>> types.FunctionType(foo1.__code__, globals(), 'somename')  

que es básicamente lo que hace el intérprete cada vez que se ejecuta una def function(..): ... instrucción.

Use __call__ para hacer que las clases personalizadas sean invocables cuando tenga sentido para su API. La clase enum.Enum() es llamable , por ejemplo, específicamente porque el uso de la syntax de llamadas le proporciona una syntax distinta de la suscripción, que se usó para otros fines. Y un objeto xmlrpc.client.ServerProxy() produce objetos de método que son instancias de _Method , porque representan una llamada remota, no una función local.

¿Existe una versión de type () pero para funciones, una metafunción?

Una especie de Las funciones tienen un tipo, y ese tipo se puede usar para construir nuevas funciones a partir de, como mínimo, un objeto de código y un diccionario global. (El objeto de código se puede construir utilizando compile() o se puede tomar de una función existente o, si es un masoquista, se puede construir a partir de una cadena de bytecode y otra información usando el constructor del tipo de código). El tipo de función no es un meta Cualquier cosa porque las funciones son instancias, no clases. Puede obtener este tipo usando type(lambda:0) (poniendo cualquier función entre paréntesis), y haga help(type(lambda:0)) para ver sus argumentos. El tipo de función es una instancia de type .

Digamos que alguien quería escribir su propia metafunción.

No puedes subclasificar el tipo de función, lo siento.

 class FunkyFunc(type(lambda: 0)): pass TypeError: Error when calling the metaclass bases type 'function' is not an acceptable base type 

¿Es el único propósito detrás de tener un objeto llamable tener una función que también pueda almacenar información en él?

Hay muchos usos para ello. Su ejemplo es uno (aunque las instancias de funciones pueden tener atributos, una clase proporciona una mejor documentación de los atributos); la otra cara, haciendo que un objeto de datos antiguo se pueda llamar, es otro (di una especie de ejemplo cobarde de eso en esta respuesta ). Puedes usar clases para escribir decoradores si las instancias son invocables. Puede usar __call__ en una metaclase para personalizar la construcción de la instancia. Y puede usarlo para escribir funciones con diferente comportamiento, como si de hecho pudiera subclasificar el tipo de función. (Si desea variables “estáticas” de estilo C en una función, puede escribirlas como una clase con un método __call__ y usar atributos para almacenar los datos estáticos).

Algo divertido de los objetos de función es que, como son invocables, ellos mismos tienen un método __call__ . Pero un método es llamable, y por lo que el método __call__ también tiene un método __call__ , y ese método __call__ (que se puede __call__ ) también tiene un método __call__ , y así sucesivamente hasta el infinito. 🙂