Python call special method praktyczny przykład

Wiem, że __call__ metoda w klasie jest wyzwalana po wywołaniu instancji klasy. Nie mam jednak pojęcia, kiedy mogę użyć tej specjalnej metody, ponieważ można po prostu utworzyć nową metodę i wykonać tę samą operację wykonaną w metodzie __call__ i zamiast wywoływać instancję, można wywołać metodę.

Byłbym naprawdę wdzięczny, gdyby ktoś podał mi praktyczne zastosowanie tej specjalnej metody.

Author: Eric, 2011-04-29

12 answers

Moduł Django forms wykorzystuje metodę __call__ do implementacji spójnego API do walidacji formularzy. Możesz napisać swój własny walidator dla formularza w Django jako funkcję.

def custom_validator(value):
    #your validation logic

Django ma domyślne wbudowane walidatory, takie jak walidatory e-mail, URL itp., które zasadniczo wchodzą w zakres walidatorów RegEx. Aby zaimplementować je czysto, Django korzysta z klas wywoływalnych (zamiast funkcji). Implementuje domyślną logikę walidacji Regex w Regexvalidatorze i następnie rozszerza te klasy dla innych walidacji.

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

Teraz zarówno twoja niestandardowa funkcja, jak i wbudowany EmailValidator mogą być wywoływane z tą samą składnią.

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

Jak widzisz, Ta implementacja w Django jest podobna do tego, co inni wyjaśnili w swoich odpowiedziach poniżej. Czy można to zrealizować w inny sposób? Mógłbyś, ale IMHO nie będzie tak czytelny ani tak łatwo rozszerzalny dla dużego frameworka jak Django.

 79
Author: Praveen Gollakota,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-28 23:53:28

Ten przykład wykorzystuje memoization , zasadniczo przechowując wartości w tabeli (w tym przypadku słownik), dzięki czemu można je później wyszukać zamiast przeliczać.

Tutaj używamy prostej klasy z metodą __call__ do obliczania współczynników (za pomocą obiekt wywoływalny ) zamiast funkcji czynnikowej, która zawiera zmienną statyczną (ponieważ nie jest to możliwe w Pythonie).

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

Teraz masz fact obiekt, który można wywołać, tak jak każda inna funkcja. Na przykład

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

i jest również stateczna.

 110
Author: S.Lott,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:55:01

Uważam, że jest to przydatne, ponieważ pozwala mi tworzyć API, które są łatwe w użyciu (masz jakiś obiekt, który wymaga pewnych konkretnych argumentów) i są łatwe do zaimplementowania, ponieważ możesz używać praktyk zorientowanych obiektowo.

Poniżej znajduje się kod, który napisałem wczoraj, który tworzy wersję hashlib.foo metod, które hashują całe pliki, a nie łańcuchy znaków:

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

Ta implementacja pozwala mi używać funkcji w sposób podobny do funkcji hashlib.foo:

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

Z oczywiście mogłem to wdrożyć w inny sposób, ale w tym przypadku wydawało się to prostym podejściem.

 35
Author: bradley.ayers,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-28 21:05:21

__call__ jest również używany do implementacji klas decorator w Pythonie. W tym przypadku instancja klasy jest wywoływana podczas wywoływania metody z dekoratorem.

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()
 20
Author: Kris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-15 13:06:16

Tak, Kiedy wiesz, że masz do czynienia z obiektami, jest całkowicie możliwe (i w wielu przypadkach wskazane) użycie jawnego wywołania metody. Czasami jednak mamy do czynienia z kodem, który oczekuje obiektów, które można wywołać - zazwyczaj funkcji, ale dzięki __call__ można budować bardziej złożone obiekty, z danymi instancji i więcej metod delegowania powtarzalnych zadań itp. nadal można je nazwać.

Ponadto, czasami używasz obu obiektów do złożonych zadań (gdzie sensowne jest napisanie dedykowanego klasy) i obiektów do prostych zadań (które już istnieją w funkcjach, lub są łatwiejsze do napisania jako funkcje). Aby mieć wspólny interfejs, musisz albo napisać małe klasy zawijające te funkcje z oczekiwanym interfejsem, albo zachować funkcje funkcje i uczynić bardziej złożone obiekty możliwymi do wywołania. Weźmy wątki jako przykład. Na Thread obiekty ze standardowego modułu libary threading żąda wywołania jako argumentu target (tzn. jako akcji do wykonania w nowym wątku). Z obiekt wywoływalny, nie jesteś ograniczony do funkcji, możesz przekazać również inne obiekty, takie jak stosunkowo złożony worker, który pobiera zadania z innych wątków i wykonuje je kolejno:

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)

To tylko przykład z głowy, ale myślę, że to już wystarczająco skomplikowane, aby uzasadnić klasę. Robienie tego tylko z funkcjami jest trudne, przynajmniej wymaga zwrócenia dwóch funkcji i to powoli staje się skomplikowane. Jeden Może zmienić nazwę __call__ na coś else I pass a bound method, ale to sprawia, że kod tworzący wątek jest nieco mniej oczywisty i nie dodaje żadnej wartości.

 9
Author: yann.kmm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-27 18:28:31

Dekoratory bazujące na klasach używają __call__ do odwoływania się do funkcji zawiniętej. Np.:

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

Jest dobry opis różnych opcji tutaj na Artima.com

 5
Author: rorycl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-28 21:12:11

IMHO __call__ metoda i zamknięcia dają nam naturalny sposób tworzenia wzorca projektowania strategii w Pythonie. Definiujemy rodzinę algorytmów, enkapsulujemy każdy z nich, czynimy je wymiennymi, a w końcu możemy wykonać wspólny zestaw kroków i na przykład obliczyć hash dla pliku.

 3
Author: ady,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-02 16:23:16

Właśnie natknąłem się na użycie __call__() w połączeniu z __getattr__(), które moim zdaniem jest piękne. Pozwala na ukrycie wielu poziomów JSON / HTTP / (however_serialized) API wewnątrz obiektu.

Część __getattr__() zajmuje się iteracyjnie zwracaniem zmodyfikowanej instancji tej samej klasy, uzupełniając jednocześnie o jeden atrybut. Następnie, po wyczerpaniu wszystkich informacji, __call__() przejmuje kontrolę nad argumentami, które przekazałeś.

Używając tego modelu, możesz na przykład wykonać połączenie podobnie jak api.v2.volumes.ssd.update(size=20), co kończy się zapytaniem PUT do https://some.tld/api/v2/volumes/ssd/update.

Dany kod jest sterownikiem blokowym dla określonego backendu woluminów w OpenStack, możesz go sprawdzić tutaj: https://github.com/openstack/cinder/blob/master/cinder/volume/drivers/nexenta/jsonrpc.py

EDIT: Zaktualizowano link do zmiany głównej.

 3
Author: Peter Slovak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-11 19:37:37

Określ __metaclass__ i nadpisaj metodę __call__, A określona metoda meta classes '__new__ zwróci instancję klasy, ponieważ masz" funkcję " z metodami.

 1
Author: user2772852,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-12 13:34:31

Możemy użyć metody __call__, Aby użyć innych metod klasy jako metod statycznych.

    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
Author: Abhishek Jain,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-05 08:33:04

Jednym z popularnych przykładów jest __call__ w functools.partial, tutaj jest uproszczona wersja (z Pythonem > = 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)

Użycie:

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

inc = partial(add, y=1)
print(inc(41))  # 42
 0
Author: endless,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-24 15:20:43

Operator wywołania funkcji.

class Foo:
    def __call__(self, a, b, c):
        # do something

x = Foo()
x(1, 2, 3)

Metoda __call _ _ może być użyta do ponownego zdefiniowania/ponownej inicjalizacji tego samego obiektu. Ułatwia również używanie instancji / obiektów klasy jako funkcji, przekazując im argumenty.

 0
Author: Kumara Krishnappa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-12 13:39:40