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