web-dev-qa-db-fra.com

Python __call__ exemple particulier de méthode spéciale

Je sais que la méthode __call__ d'une classe est déclenchée lorsque l'instance d'une classe est appelée. Cependant, je n'ai aucune idée du moment où je peux utiliser cette méthode spéciale, car on peut simplement créer une nouvelle méthode et effectuer la même opération que dans la méthode __call__ et au lieu d'appeler l'instance, vous pouvez appeler la méthode.

J'apprécierais vraiment que quelqu'un me donne une utilisation pratique de cette méthode spéciale.

143
mohi666

Le module de formulaires Django utilise la méthode __call__ pour implémenter une API cohérente pour la validation de formulaire. Vous pouvez écrire votre propre validateur pour un formulaire dans Django en tant que fonction.

def custom_validator(value):
    #your validation logic

Django a quelques validateurs intégrés par défaut, tels que des validateurs de courrier électronique, des validateurs d'URL, etc. Pour les implémenter proprement, Django a recours à des classes appelables (au lieu de fonctions). Il implémente la logique de validation Regex par défaut dans un RegexValidator, puis étend ces classes pour d'autres validations.

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

Désormais, votre fonction personnalisée et EmailValidator intégré peuvent être appelés avec la même syntaxe.

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

Comme vous pouvez le constater, cette implémentation dans Django est similaire à ce que d’autres ont expliqué dans leurs réponses ci-dessous. Cela peut-il être mis en œuvre d'une autre manière? Vous pourriez le faire, mais à mon humble avis, il ne sera pas aussi lisible ni aussi facilement extensible que pour un gros framework comme Django.

82
Praveen Gollakota

Cet exemple utilise memoization , stockant essentiellement des valeurs dans une table (dictionnaire dans ce cas) afin que vous puissiez les rechercher plus tard au lieu de les recalculer.

Ici, nous utilisons une classe simple avec une méthode __call__ pour calculer des factorielles (via un objet appelable ) au lieu d'une fonction factorielle contenant une variable statique (comme ce n'est pas possible 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()

Vous avez maintenant un objet fact qui est appelable, comme toutes les autres fonctions. Par exemple

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

Et il est également stateful.

113
S.Lott

Je trouve cela utile car cela me permet de créer des API faciles à utiliser (vous avez un objet appelable qui nécessite des arguments spécifiques) et faciles à implémenter car vous pouvez utiliser des pratiques orientées objet.

Voici le code que j'ai écrit hier et qui crée une version des méthodes hashlib.foo qui hache des fichiers entiers plutôt que des chaînes:

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

Cette implémentation me permet d’utiliser les fonctions de la même manière que les fonctions hashlib.foo:

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

Bien sûr, j'aurais pu l'appliquer de manière différente, mais dans ce cas, cela semblait être une approche simple.

37
bradley.ayers

__call__ est également utilisé pour implémenter des classes de décorateur en python. Dans ce cas, l'instance de la classe est appelée lorsque la méthode avec le décorateur est appelée.

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 __== "__main__":
    hello()
20
Kris

Oui, lorsque vous savez que vous utilisez des objets, il est parfaitement possible (et souvent conseillé) d’utiliser un appel de méthode explicite. Cependant, vous traitez parfois avec du code qui attend des objets appelables - généralement des fonctions, mais grâce à __call__, vous pouvez construire des objets plus complexes, avec des données d'instance et davantage de méthodes pour déléguer des tâches répétitives, etc.

De même, vous utilisez parfois à la fois des objets pour des tâches complexes (où il est judicieux d'écrire une classe dédiée) et des objets pour des tâches simples (qui existent déjà dans des fonctions ou sont plus facilement écrits en tant que fonctions). Pour avoir une interface commune, vous devez soit écrire de petites classes qui enveloppent ces fonctions avec l'interface attendue, soit conserver les fonctions de fonctions et rendre les objets plus complexes appelables. Prenons les discussions comme exemple. Les objets Thread DU MODULE DE BIBLIOTH&EGRAVE;QUE STANDARD threading veulent un appelable comme argument target (c’est-à-dire comme une action à effectuer dans le nouveau thread). Avec un objet appelable, vous n'êtes pas limité aux fonctions, vous pouvez également transmettre d'autres objets, tels qu'un opérateur relativement complexe qui doit exécuter des tâches à partir d'autres threads et les exécuter de manière séquentielle:

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)

Ce n’est qu’un exemple qui me vient à l’esprit, mais je pense que c’est déjà suffisamment complexe pour justifier le cours. Faire cela uniquement avec des fonctions est difficile, du moins cela nécessite le renvoi de deux fonctions et cela devient lentement complexe. Un pourrait renommer __call__ en quelque chose d'autre et passer une méthode liée, mais cela rend le code créant le thread légèrement moins évident, et n'ajoute aucune valeur.

9
user395760

Les décorateurs basés sur les classes utilisent __call__ pour faire référence à la fonction encapsulée. Par exemple.:

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

Vous trouverez une bonne description des différentes options ici à Artima.com

5
rorycl

La méthode IMHO __call__ et les fermetures nous offrent un moyen naturel de créer un modèle de conception STRATEGY en Python. Nous définissons une famille d'algorithmes, encapsulons chacun d'entre eux, les rendons interchangeables et nous pouvons finalement exécuter un ensemble d'étapes communes et, par exemple, calculer un hachage pour un fichier. 

3
ady

Je suis juste tombé sur une utilisation de __call__() de concert avec __getattr__() qui, à mon avis, est magnifique. Il vous permet de masquer plusieurs niveaux d'une API JSON/HTTP/(cependant_serialized) à l'intérieur d'un objet.

La partie __getattr__() se charge de renvoyer de manière itérative une instance modifiée de la même classe, en remplissant un attribut supplémentaire à la fois. Ensuite, une fois que toutes les informations ont été épuisées, __call__() reprend tous les arguments que vous avez passés.

En utilisant ce modèle, vous pouvez par exemple faire un appel comme api.v2.volumes.ssd.update(size=20), qui aboutit à une demande PUT à https://some.tld/api/v2/volumes/ssd/update.

Le code particulier est un pilote de stockage de bloc pour un volume de volume donné dans OpenStack, vous pouvez le vérifier ici: https://github.com/openstack/cinder/blob/master/cinder/volume/drivers/nexenta/jsonrpc .py

EDIT: Mise à jour du lien pour pointer vers la révision principale.

3
Peter Slovak

Nous pouvons utiliser la méthode __call__ pour utiliser d'autres méthodes de classe en tant que méthodes statiques.

    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")             
1
Abhishek Jain

Spécifiez un __metaclass__, remplacez la méthode __call__ et faites en sorte que la méthode __new__ de la méta-classe spécifiée retourne une instance de la classe. Si vous ne respectez pas ces règles, vous disposez d'une "fonction" avec des méthodes.

1
user2772852

Un exemple courant est le __call__ dans functools.partial, voici une version simplifiée (avec 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)

Usage:

def add(x, y):
    return x + y

inc = partial(add, y=1)
print(inc(41))  # 42
0
endless

Je trouve un bon endroit pour utiliser des objets appelables, ceux qui définissent __call__(), lorsque vous utilisez les fonctionnalités de programmation fonctionnelle en Python, telles que map(), filter(), reduce()

Le meilleur moment pour utiliser un objet appelable sur une fonction simple ou une fonction lambda est lorsque la logique est complexe et doit conserver un état ou utiliser d'autres informations qui ne sont pas transmises à la fonction __call__().

Voici un code qui filtre les noms de fichiers en fonction de leur extension en utilisant un objet appelable et 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)

Usage:

filenames = [
    'me.jpg',
    'me.txt',
    'friend1.jpg',
    'friend2.bmp',
    'you.jpeg',
    'you.xml']

acceptor = ImageFileAcceptor()
image_filenames = filter(acceptor, filenames)
print image_filenames

Sortie:

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