Python __call__ método especial ejemplo práctico

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 puede simplemente crear un nuevo método y realizar la misma operación realizada en el método __call__ y, en lugar de llamar a la instancia, puede llamar al método.

Realmente lo agradecería si alguien me diera un uso práctico de este método especial.

Related of "Python __call__ método especial ejemplo práctico"

El módulo de formularios de Django usa el método __call__ muy bien 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 predeterminados, como validadores de correo electrónico, validadores de URL, etc., que en general están bajo el paraguas de los validadores RegEx. Para implementar estos de manera limpia, Django recurre a clases que se pueden llamar (en lugar de funciones). Implementa la lógica de Validación de Regex predeterminada en un RegexValidator y luego extiende 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 llamar tanto a su función personalizada como a EmailValidator incorporado con la misma syntax.

 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ía, pero en mi humilde opinión no será tan legible o tan fácilmente extensible para un marco grande como Django.

Este ejemplo utiliza la memorización , básicamente almacenando valores en una tabla (diccionario en este caso) para que pueda buscarlos más tarde en lugar de recalcularlos.

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 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 tienes un objeto de fact que se puede llamar, al igual que 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 un estado.

Me parece útil porque me permite crear API que son fáciles de usar (tienes un objeto que se puede llamar que requiere algunos argumentos específicos) y son fáciles de implementar porque puedes 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 hashlib.foo hash de 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, podría haberlo implementado de una manera diferente, pero en este caso parecía un enfoque simple.

__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() 

Sí, cuando sabe que está tratando con objetos, es perfectamente posible (y en muchos casos recomendable) utilizar una llamada de método explícita. Sin embargo, a veces se trata de código que espera objetos que se pueden __call__ , normalmente funciona, pero gracias a __call__ puede crear 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, a veces se usan objetos para tareas complejas (donde tiene sentido escribir una clase dedicada) y 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 que envuelvan esas funciones con la interfaz esperada, o mantener las funciones de las funciones y hacer que los objetos más complejos sean exigibles. Tomemos los hilos como ejemplo. Los objetos de Thread de los threading módulo de biblioteca estándar desean un invocable como argumento de target (es decir, como acción a realizar en el nuevo subproceso). Con un objeto exigible, no está restringido a funciones, también puede pasar otros objetos, como un trabajador relativamente complejo que obtiene tareas para realizar desde otros subprocesos y los ejecuta de forma secuencial:

 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 solo un ejemplo de mi cabeza, pero creo que ya es lo suficientemente complejo como para justificar a la clase. Hacer esto solo con funciones es difícil, al menos requiere que se devuelvan dos funciones y eso se está volviendo complejo lentamente. Uno podría cambiar el nombre de __call__ a otra cosa y pasar un método enlazado, pero eso hace que el código que crea el hilo sea un poco menos obvio, y no agrega ningún valor.

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

En mi __call__ método y los cierres de __call__ 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.

Me topé con el uso de __call__() en concierto con __getattr__() que creo que es hermoso. Le permite ocultar múltiples niveles de una API JSON / HTTP / (sin embargo, se serializa) dentro de un objeto.

La parte __getattr__() se encarga 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 cualquier argumento 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 en particular es un controlador de almacenamiento en bloque para un cierto volumen de backend en OpenStack, puede verlo aquí: https://github.com/openstack/cinder/blob/master/volum/drivers/nexenta/jsonrpc.py

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

Especifique un __metaclass__ e __call__ método __call__ , y __call__ método __call__ las meta-clases __new__ devuelva una instancia de la clase, viola que tiene una “función” con los métodos.

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") 

Un ejemplo común es __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 

El operador de llamada de función.

 class Foo: def __call__(self, a, b, c): # do something x = Foo() x(1, 2, 3) 

El método __call__ se puede usar para redefinir / reinicializar el mismo objeto. También facilita el uso de instancias / objetos de una clase como funciones al pasar argumentos a los objetos.

Encuentro un buen lugar para usar objetos que se pueden __call__() , aquellos que definen __call__() , es cuando se usan las capacidades de progtwigción funcional en Python, como map() , filter() , reduce() .

El mejor momento para usar un objeto que se puede llamar sobre una función simple o una función lambda es cuando la lógica es compleja y necesita conservar algún estado o usa otra información que no se pasa a la función __call__() .

Aquí hay un código que filtra los nombres de los archivos en función de la extensión de su nombre de archivo utilizando un objeto y filter() .

Callable

 import os class FileAcceptor(object): def __init__(self, accepted_extensions): self.accepted_extensions = accepted_extensions def __call__(self, filename): base, ext = os.path.splitext(filename) return ext in self.accepted_extensions class ImageFileAcceptor(FileAcceptor): def __init__(self): image_extensions = ('.jpg', '.jpeg', '.gif', '.bmp') super(ImageFileAcceptor, self).__init__(image_extensions) 

Uso:

 filenames = [ 'me.jpg', 'me.txt', 'friend1.jpg', 'friend2.bmp', 'you.jpeg', 'you.xml'] acceptor = ImageFileAcceptor() image_filenames = filter(acceptor, filenames) print image_filenames 

Salida:

 ['me.jpg', 'friend1.jpg', 'friend2.bmp', 'you.jpeg']