str method magic len getitem data __eq__ __dict__ python methods call magic-methods

method - Python__call__ método especial ejemplo práctico



getitem python (11)

Sé que el método __call__ en una clase se activa cuando se llama a la instancia de una clase. Sin embargo, no tengo idea de cuándo puedo usar este método especial, porque uno simplemente puede crear un nuevo método y realizar la misma operación en el método __call__ y en lugar de llamar a la instancia, puede llamar al método.

Realmente apreciaría que alguien me diera un uso práctico de este método especial.


Acabo de tropezar con un uso de __call__() en concierto con __getattr__() que creo que es hermoso. Le permite ocultar varios niveles de una API JSON / HTTP / (however_serialized) dentro de un objeto.

La parte __getattr__() se ocupa de devolver iterativamente una instancia modificada de la misma clase, completando un atributo más a la vez. Luego, después de que se haya agotado toda la información, __call__() hace cargo de los argumentos que haya pasado.

Usando este modelo, puede, por ejemplo, hacer una llamada como api.v2.volumes.ssd.update(size=20) , que termina en una solicitud PUT a https://some.tld/api/v2/volumes/ssd/update .

El código particular es un controlador de almacenamiento en bloque para un determinado backend de volumen en OpenStack, puede consultarlo aquí: https://github.com/openstack/cinder/blob/master/cinder/volume/drivers/nexenta/jsonrpc.py

EDITAR: actualizó el enlace para apuntar a la revisión maestra.


El módulo de formularios de Django utiliza el método __call__ para implementar una API consistente para la validación de formularios. Puede escribir su propio validador para un formulario en Django como una función.

def custom_validator(value): #your validation logic

Django tiene algunos validadores incorporados por defecto, como validadores de correo electrónico, validadores de URL, etc., que en general caen bajo el paraguas de los validadores de RegEx. Para implementar esto limpiamente, Django recurre a clases llamables (en lugar de funciones). Implementa la lógica de Validación de Regex por defecto en un RegexValidator y luego amplía estas clases para otras validaciones.

class RegexValidator(object): def __call__(self, value): # validation logic class URLValidator(RegexValidator): def __call__(self, value): super(URLValidator, self).__call__(value) #additional logic class EmailValidator(RegexValidator): # some logic

Ahora se puede invocar tanto la función personalizada como el EmailValidator incorporado con la misma sintaxis.

for v in [custom_validator, EmailValidator()]: v(value) # <-----

Como puede ver, esta implementación en Django es similar a lo que otros han explicado en sus respuestas a continuación. ¿Se puede implementar esto de otra manera? Podrías, pero en mi humilde opinión no será tan legible ni tan fácilmente extensible para un gran framework como Django.


En mi humilde __call__ método y los cierres nos dan una forma natural de crear un patrón de diseño de ESTRATEGIA en Python. Definimos una familia de algoritmos, encapsulamos cada uno, los hacemos intercambiables y al final podemos ejecutar un conjunto común de pasos y, por ejemplo, calcular un hash para un archivo.


Especifique una __metaclass__ y anule el método __call__ , y __call__ las __call__ especificadas '' __new__ método devuelvan una instancia de la clase, viola tiene una'' función ''con métodos.


Este ejemplo utiliza la memoization , básicamente almacenando valores en una tabla (diccionario en este caso) para que pueda buscarlos más tarde en lugar de volver a calcularlos.

Aquí usamos una clase simple con un método __call__ para calcular factoriales (a través de un objeto invocable ) en lugar de una función factorial que contiene una variable estática (ya que eso no es posible en Python).

class Factorial: def __init__(self): self.cache = {} def __call__(self, n): if n not in self.cache: if n == 0: self.cache[n] = 1 else: self.cache[n] = n * self.__call__(n-1) return self.cache[n] fact = Factorial()

Ahora tiene un objeto de fact que es invocable, como cualquier otra función. Por ejemplo

for i in xrange(10): print("{}! = {}".format(i, fact(i))) # output 0! = 1 1! = 1 2! = 2 3! = 6 4! = 24 5! = 120 6! = 720 7! = 5040 8! = 40320 9! = 362880

Y también es con estado.


Lo encuentro útil porque me permite crear API que son fáciles de usar (tiene algún objeto invocable que requiere algunos argumentos específicos) y son fáciles de implementar porque puede usar prácticas orientadas a objetos.

El siguiente es el código que escribí ayer que hace una versión de los métodos hashlib.foo que hash archivos completos en lugar de cadenas:

# filehash.py import hashlib class Hasher(object): """ A wrapper around the hashlib hash algorithms that allows an entire file to be hashed in a chunked manner. """ def __init__(self, algorithm): self.algorithm = algorithm def __call__(self, file): hash = self.algorithm() with open(file, ''rb'') as f: for chunk in iter(lambda: f.read(4096), ''''): hash.update(chunk) return hash.hexdigest() md5 = Hasher(hashlib.md5) sha1 = Hasher(hashlib.sha1) sha224 = Hasher(hashlib.sha224) sha256 = Hasher(hashlib.sha256) sha384 = Hasher(hashlib.sha384) sha512 = Hasher(hashlib.sha512)

Esta implementación me permite usar las funciones de manera similar a las funciones hashlib.foo :

from filehash import sha1 print sha1(''somefile.txt'')

Por supuesto que podría haberlo implementado de una manera diferente, pero en este caso me pareció un enfoque simple.


Los decoradores basados ​​en __call__ usan __call__ para hacer referencia a la función envuelta. P.ej:

class Deco(object): def __init__(self,f): self.f = f def __call__(self, *args, **kwargs): print args print kwargs self.f(*args, **kwargs)

Hay una buena descripción de las diversas opciones aquí en Artima.com


Podemos usar el método __call__ para usar otros métodos de clase como métodos estáticos.

class _Callable: def __init__(self, anycallable): self.__call__ = anycallable class Model: def get_instance(conn, table_name): """ do something""" get_instance = _Callable(get_instance) provs_fac = Model.get_instance(connection, "users")


Sí, cuando sabes que estás tratando con objetos, es perfectamente posible (y en muchos casos aconsejable) utilizar una llamada a un método explícito. Sin embargo, a veces se trata de código que espera objetos invocables, normalmente funciones, pero gracias a __call__ puede construir objetos más complejos, con datos de instancia y más métodos para delegar tareas repetitivas, etc. que aún se pueden llamar.

Además, algunas veces está usando tanto objetos para tareas complejas (donde tiene sentido escribir una clase dedicada) como objetos para tareas simples (que ya existen en funciones o que se escriben más fácilmente como funciones). Para tener una interfaz común, debe escribir pequeñas clases envolviendo esas funciones con la interfaz esperada, o mantener las funciones de funciones y hacer que los objetos más complejos sean llamados. Tomemos los hilos como ejemplo. Los objetos Thread del threading módulo estándar de libary threading un argumento invocable como target (es decir, como acción a realizar en el nuevo hilo). Con un objeto invocable, no está restringido a las funciones, puede pasar otros objetos también, como un trabajador relativamente complejo que obtiene tareas de otros hilos y las ejecuta secuencialmente:

class Worker(object): def __init__(self, *args, **kwargs): self.queue = queue.Queue() self.args = args self.kwargs = kwargs def add_task(self, task): self.queue.put(task) def __call__(self): while True: next_action = self.queue.get() success = next_action(*self.args, **self.kwargs) if not success: self.add_task(next_action)

Esto es sólo un ejemplo fuera de lo común, pero creo que ya es lo suficientemente complejo como para garantizar la clase. Hacer esto solo con funciones es difícil, al menos requiere devolver dos funciones y eso lentamente se está volviendo complejo. Uno podría renombrar __call__ a otra cosa y pasar un método enlazado, pero eso hace que el código que crea el hilo sea menos obvio y no agrega ningún valor.


Un ejemplo común es el __call__ en functools.partial , aquí hay una versión simplificada (con Python> = 3.5):

class partial: """New function with partial application of the given arguments and keywords.""" def __new__(cls, func, *args, **kwargs): if not callable(func): raise TypeError("the first argument must be callable") self = super().__new__(cls) self.func = func self.args = args self.kwargs = kwargs return self def __call__(self, *args, **kwargs): return self.func(*self.args, *args, **self.kwargs, **kwargs)

Uso:

def add(x, y): return x + y inc = partial(add, y=1) print(inc(41)) # 42


__call__ también se usa para implementar clases de decorador en python. En este caso, se llama a la instancia de la clase cuando se llama al método con el decorador.

class EnterExitParam(object): def __init__(self, p1): self.p1 = p1 def __call__(self, f): def new_f(): print("Entering", f.__name__) print("p1=", self.p1) f() print("Leaving", f.__name__) return new_f @EnterExitParam("foo bar") def hello(): print("Hello") if __name__ == "__main__": hello()