Практический пример специального метода вызова Python
Я знаю, что __call__
метод в классе запускается при вызове экземпляра класса. Однако я понятия не имею, когда я могу использовать этот специальный метод, потому что можно просто создать новый метод и выполнить ту же операцию, выполненную в __call__
метод и вместо вызова экземпляра, вы можете вызвать метод.
Я был бы очень признателен, если бы кто-то дал мне практическое использование этого специального метода.
12 ответов
модуль Django forms использует __call__
метод красиво реализовать согласованный API для проверки формы. Вы можете написать свой собственный валидатор для формы в Django в качестве функции.
def custom_validator(value):
#your validation logic
Django имеет некоторые встроенные валидаторы по умолчанию, такие как валидаторы электронной почты, валидаторы url и т. д., которые в целом попадают под зонт валидаторов RegEx. Чтобы реализовать их чисто, Django прибегает к вызываемым классам (вместо функций). Он реализует логику проверки Regex по умолчанию в a RegexValidator, а затем расширяет эти классы для других проверок.
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
теперь ваша пользовательская функция и встроенный EmailValidator могут вызываться с тем же синтаксисом.
for v in [custom_validator, EmailValidator()]:
v(value) # <-----
как вы можете видеть, эта реализация в Django похожа на то, что другие объяснили в своих ответах ниже. Это может быть реализовано каким-либо другим способом? Вы могли бы, но IMHO это не будет так читаемо или так легко расширяемо для большой структуры, как Django.
в этом примере используется memoization, в основном сохраняя значения в таблице (словарь в этом случае), чтобы вы могли искать их позже, а не пересчитывать их.
здесь мы используем простой класс с __call__
метод расчета факториалов (через callable объект) вместо факторной функции, содержащей статическую переменную (поскольку это невозможно в 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()
теперь у вас есть fact
объект, который вызывается, просто как и любая другая функция. Например
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
и это также stateful.
Я нахожу это полезным, потому что это позволяет мне создавать API, которые просты в использовании (у вас есть некоторый вызываемый объект, который требует определенных аргументов), и просты в реализации, потому что вы можете использовать объектно-ориентированные практики.
ниже приведен код, который я написал вчера, что делает версию hashlib.foo
методы, которые хэшируют целые файлы, а не строки:
# 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)
эта реализация позволяет мне использовать функции аналогично hashlib.foo
функции:
from filehash import sha1
print sha1('somefile.txt')
конечно, я мог бы реализовать его по-другому, но в этом случае это казалось простым подходом.
__call__
также используется для реализации классов декораторов в python. В этом случае экземпляр класса вызывается при вызове метода с декоратором.
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()
Да, когда вы знаете, что имеете дело с объектами, вполне возможно (и во многих случаях целесообразно) использовать явный вызов метода. Однако иногда вы имеете дело с кодом, который ожидает вызываемые объекты-обычно функции, но благодаря __call__
вы можете создавать более сложные объекты, с данными экземпляра и больше методов для делегирования повторяющихся задач и т. д. что еще вызывать.
кроме того, иногда вы используете оба объекта для сложных задач (где имеет смысл писать выделенный класс) и объекты для простых задач (которые уже существуют в функциях или более легко записываются как функции). Чтобы иметь общий интерфейс, вам нужно либо написать крошечные классы, обертывающие эти функции ожидаемым интерфейсом, либо сохранить функции и сделать более сложные объекты вызываемыми. Возьмем, к примеру, темы. The Thread
объекты из стандартного модуля libary threading
хочу вызвать как target
аргумент (т. е. как действие, которое нужно сделать в новом потоке). С вызываемым объектом вы не ограничены функциями, вы также можете передавать другие объекты, такие как относительно сложный работник, который получает задачи из других потоков и выполняет их последовательно:
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)
это просто пример с моей головы, но я думаю, что он уже достаточно сложен, чтобы оправдать класс. Делать это только с функциями сложно, по крайней мере, это требует возврата двух функций, и это медленно усложняется. Один мог бы переименовать __call__
к чему-то еще и передать связанный метод, но это делает код, создающий поток, немного менее очевидным и не добавляет никакого значения.
класс на основе декораторов использовать __call__
для ссылки на функцию-оболочку. Например:
class Deco(object):
def __init__(self,f):
self.f = f
def __call__(self, *args, **kwargs):
print args
print kwargs
self.f(*args, **kwargs)
есть хорошее описание различных вариантов здесь Artima.com
ИМХО __call__
метод и закрытие дают нам естественный способ создания шаблона дизайна стратегии в Python. Мы определяем семейство алгоритмов, инкапсулируем каждый из них, делаем их взаимозаменяемыми и в итоге можем выполнить общий набор шагов и, например, вычислить хэш для файла.
Я просто наткнулся на использование __call__()
совместно с __getattr__()
который я считаю красивым. Он позволяет скрыть несколько уровней API JSON/HTTP / (however_serialized) внутри объекта.
на __getattr__()
part заботится об итеративном возврате измененного экземпляра того же класса, заполняя одновременно еще один атрибут. Затем, после того, как вся информация была исчерпана, __call__()
берет на себя все аргументы, которые вы передали.
используя эту модель, вы может, например, сделать вызов как api.v2.volumes.ssd.update(size=20)
, который заканчивается запросом PUT на https://some.tld/api/v2/volumes/ssd/update
.
конкретный код-это драйвер блочного хранилища для определенного бэкэнда Тома в OpenStack, вы можете проверить его здесь: https://github.com/openstack/cinder/blob/master/cinder/volume/drivers/nexenta/jsonrpc.py
EDIT: обновил ссылку на мастер доработку.
указать __metaclass__
и переопределить __call__
метод, и имеют указанные мета-классы'__new__
метод возвращает экземпляр класса, viola у вас есть "функция" с методами.
можно использовать __call__
метод для использования других методов класса в качестве статических методов.
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")
одним из распространенных примеров является __call__
на functools.partial
, вот упрощенная версия (с 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)
использование:
def add(x, y):
return x + y
inc = partial(add, y=1)
print(inc(41)) # 42
оператор вызова функции.
class Foo:
def __call__(self, a, b, c):
# do something
x = Foo()
x(1, 2, 3)
на __call__ метод может использоваться для переопределения / повторной инициализации одного и того же объекта. Это также облегчает использование экземпляров / объектов класса в качестве функций путем передачи аргументов объектам.