Historia de la cuestión:
En Python, todo es un objeto, y cualquier función también es un objeto. Se implementó un mecanismo que permite a una instancia de clase comportarse como una función. Para ello se introdujo el método mágico call, que permite hacer que los objetos sean invocables.
Problema:
A veces es necesario pasar un objeto que puede realizar acciones cuando se invoca, por ejemplo, funciones con estado (funciones con estado), cierres, objetos manejadores de eventos, etc. El desarrollo de la arquitectura requiere comprender cómo implementar correctamente esta funcionalidad.
Solución:
Al implementar el método call en una clase, se puede hacer que su instancia sea invocable "como una función". Esto permite combinar las capacidades de las clases (encapsulación de estado, herencia, métodos) y de las funciones (invocabilidad). Este enfoque se utiliza para crear objetos comando, manejadores complejos, envolturas, etc.
Ejemplo de código:
class Adder: def __init__(self, x): self.x = x def __call__(self, y): return self.x + y add5 = Adder(5) print(add5(10)) # Imprimirá 15
Características clave:
¿Hereda el método call los atributos de una función normal, como name y doc?
No, el objeto con el método call no tendrá el atributo name (o lo tomará de la clase). Los metadatos de las funciones no se conservan.
¿Es un objeto con call una función real?
No, es una instancia de clase, no una función. Solo implementa el comportamiento de "invocabilidad". Por ejemplo, compararlo con una función usando isinstance(obj, types.FunctionType) dará False.
¿Se puede aplicar un decorador diseñado para funciones a un objeto con call?
Normalmente, tales decoradores esperan exactamente una función, no un objeto (por ejemplo, functools.lru_cache). Su uso puede llevar a errores o simplemente no funcionar.
Ventajas:
Caso negativo: En el proyecto se implementó un registrador a través de una clase con call, para almacenar configuraciones (nivel, nombre de archivo). Sin embargo, se olvidaron de que las funciones manejadoras de señales esperan exactamente una función, y obtuvieron errores al registrar el manejador (object is not a function).
Ventajas: configuración flexible del registrador. Desventajas: incompatibilidad con las interfaces esperadas.
Caso positivo: En otro proyecto se utilizó una clase con call para crear funciones decoradoras complejas, manteniendo parámetros, lo que simplificó la prueba.
Ventajas: extendibilidad, conveniencia. Desventajas: más código en comparación con una función o lambda.