python instance

python - ¿Por qué no agregar dinámicamente un método `__call__` a una instancia de trabajo?



instance (2)

Tanto en Python 2 como en Python 3, el código:

class Foo(object): pass f = Foo() f.__call__ = lambda *args : args f(1, 2, 3)

devuelve como error El Foo object is not callable . ¿Por qué sucede eso?

PD: con clases de estilo antiguo funciona como se esperaba.

PPS: Este comportamiento es intencionado (ver respuesta aceptada). Como __call__ , es posible definir una __call__ a nivel de clase que solo reenvía a otro miembro y establece este miembro "normal" en una implementación de __call__ por instancia.


En las nuevas clases de estilo (por defecto 3.x ) y heredadas del objeto en 2.x los métodos de interceptación de atributos __getattr__ y __getattribute__ ya no se __getattribute__ para operaciones integradas en métodos de instancias sobrecargadas de dunder y, en cambio, la búsqueda comienza en la clase.

La razón detrás de esto implica una tecnicidad introducida por la presencia de MetaClasses.

Como las clases son instancias de metaclases y porque las metaclases pueden definir ciertas operaciones que actúan sobre las clases, omitir la clase (que en este caso puede considerarse una instance ) tiene sentido; debe invocar el método definido en la metaclase que se define para procesar clases ( cls como primer argumento). Si se utilizó la búsqueda de instancias, la búsqueda usaría el método de clase que se define para las instancias de esa clase ( self ).

Otra (razón en disputa ) implica la optimización : dado que las operaciones integradas en instancias generalmente se invocan con mucha frecuencia, omitir la búsqueda de instancias por completo e ir directamente a la clase nos ahorra algo de tiempo. [ Fuente Lutz, Learning Python, 5ª edición ]

El área principal donde esto podría ser un inconveniente es cuando se crean objetos proxy que sobrecargan __getattr__ y __getattribute__ con la intención de reenviar las llamadas al objeto interno incrustado. Dado que la invocación integrada omitirá la instancia por completo, no quedarán atrapados y, en consecuencia, no se reenviarán al objeto incrustado.

Una solución fácil, pero tediosa, para esto es sobrecargar realmente todos los dunders que desea interceptar en el objeto proxy. Luego, en estos dunders sobrecargados, puede delegar la llamada al objeto interno según sea necesario.

La solución más fácil que se me ocurre es establecer el atributo en la clase Foo con setattr :

setattr(Foo, ''__call__'', lambda *args: print(args)) f(1, 2, 3) (<__main__.Foo object at 0x7f40640faa90>, 1, 2, 3)


Los métodos de doble subrayado siempre se buscan en la clase, nunca en la instancia. Consulte Búsqueda de métodos especiales para clases de estilo nuevo :

Para las clases de estilo nuevo, las invocaciones implícitas de métodos especiales solo funcionan correctamente si se definen en el tipo de un objeto, no en el diccionario de instancias del objeto.

Esto se debe a que el tipo puede necesitar admitir la misma operación (en cuyo caso, se busca el método especial en el metatipo).

Por ejemplo, las clases son invocables (así es como se produce una instancia), pero si Python buscó el método __call__ en el objeto real , nunca podría hacerlo en las clases que implementan __call__ para sus instancias. ClassObject() se convertiría en ClassObject.__call__() lo que fallaría porque el parámetro self no se pasa al método self . Entonces, en su lugar, se type(ClassObject).__call__(ClassObject) , y llamar a instance() traduce como type(instance).__call__(instance) .

Para evitar esto, puede agregar un método __call__ a la clase que verifica si hay un atributo __call__ en la clase y, si existe, lo llama.