¿Cómo funciona realmente __call__?

El método mágico de Python __call__ se llama cuando intentas llamar a un objeto. Cls()() es, por lo tanto, igual a Cls.__call__(Cls()) .

Las funciones son objetos de primera clase en Python, lo que significa que solo son objetos que se pueden __call__ (usando __call__ ). Sin embargo, __call__ sí misma es una función, por lo que también tiene __call__ , que nuevamente tiene su propio __call__ , que también tiene su propia __call__ .

Por lo tanto, Cls.__call__(Cls()) es igual a Cls.__call__.__call__(Cls()) y de nuevo es igual a Cls.__call__.__call__.__call__(Cls()) y así sucesivamente.

¿Cómo termina este bucle infinito? ¿Cómo __call__ realmente ejecuta el código?

Bajo el capó, todas las llamadas en Python utilizan el mismo mecanismo, y casi todas llegan a la misma función C en la implementación de CPython. Si un objeto es una instancia de una clase con un método __call__ , una función (en sí misma un objeto) o un objeto incorporado, todas las llamadas (excepto los casos especiales optimizados) llegan a la función PyObject_Call . Esa función C obtiene el tipo de objeto del campo ob_type de la estructura PyObject del objeto, y luego del tipo (otra estructura PyObject ) obtiene el campo tp_call , que es un puntero a función. Si tp_call no es NULL , llama a través de eso , con las estructuras args y kwargs que también se pasaron a PyObject_Call .

Cuando una clase define un método __call__ , eso configura el campo tp_call apropiada.

Aquí hay un artículo que explica todo esto en detalle: Elementos internos de Python: cómo funcionan los callables . Incluso enumera y explica la función completa de PyObject_Call , que no es muy grande. Si desea ver esa función en su hábitat nativo, se encuentra en Objetos / abstract.c en el repository CPython.

También es relevante esta Q&A de stackoverflow: ¿Qué es un “llamable” en Python? .

No hay un bucle infinito real, porque el método __call__ no se invoca (“llama”) en realidad para todas esas situaciones. Solo se invoca directamente cuando hay una llamada similar a una función en un objeto que proporciona un método __call__ .

Las instancias de clase normal Cls(...) y la invocación funcional regular f() son casos conocidos que se manejan directamente. En general, no hay una invocación real de __call__() , así que hay un número finito de __call__ método __call__ que pueden ocurrir, incluso en casos complejos con herencia profunda, metaclases, etc.

Debido a que existía cierta controversia sobre si realmente estaba ocurriendo el cortocircuito de los bucles infinitos conceptuales, veamos el código de bytes desensamblado. Considere el siguiente código:

 def f(x): return x + 1 class Adder(object): def something(self, x): return x + 19 def __call__(self, x): return x + 1 def lotsacalls(y): u = f(1) a = Adder() z = u + a.something(y) return a(z * 10) 

Lo siento, es un poco complejo, ya que quiero mostrar varias instancias de cortocircuitos, como las funciones de def normales, __init__ llamadas __init__ , los métodos normales y los métodos especiales __call__ . Ahora:

desmontaje anotado

Así que aquí hay un rango de veces en que, si Python realmente estuviera “caminando por el árbol” de las invocaciones __call__ conceptuales, haría referencia a la Function (y posiblemente a las clases de Method , e invocaría sus métodos __call__ ). No lo hace Utiliza el simple bytecode CALL_FUNCTION en todos los casos, cortocircuitando el árbol conceptual hacia abajo. Lógicamente , puede imaginar que hay una Function clase que tiene un método __call__ que se invoca cuando se llama a una función (es decir, una instancia de la clase de Function ). Pero realmente no funciona de esa manera. El comstackdor, el intérprete de bytecode y otras partes de las bases en lenguaje C no caminan realmente en los árboles de metaclases. Ellos cortocircuitan como locos.

No __call__ ninguna documentación, pero en mis pruebas parece que __call__ no siempre se llama:

 def func1(*args, **kargs): print "func1 called", args, kargs def func2(*args, **kargs): print "func2 called", args, kargs func1.__call__ = func2 func1() # here is still called func1 class Cls: def __init__(*args, **kargs): print "init called", args, kargs def __call__(*args, **kargs): print "object called", args, kargs obj = Cls() # here is actually called __init__ obj() # here is called __call__ 

esto imprime

 func1 called () {} init called (<__main__.Cls instance at 0x0000000002A5ED88>,) {} object called (<__main__.Cls instance at 0x0000000002A5ED88>,) {}