Python вызов специального метода практический пример
Я знаю, что __call__ метод в классе запускается при вызове экземпляра класса. Однако я понятия не имею, когда я могу использовать этот специальный метод, потому что можно просто создать новый метод и выполнить ту же операцию, что и в __call__ метод и вместо вызова экземпляра, вы можете вызвать метод.
Я был бы очень признателен, если кто-то дает практическое использование этого специальный метод.
12 ответов:
модуль Django forms использует
__call__метод красиво реализовать согласованный API для проверки формы. Вы можете написать свой собственный валидатор для формы в Django в качестве функции.def custom_validator(value): #your validation logicDjango имеет некоторые встроенные валидаторы по умолчанию, такие как валидаторы электронной почты, валидаторы url и т. д., которые в целом подпадают под зонтик валидаторов регулярных выражений. Чтобы реализовать их чисто, Django прибегает к вызываемым классам (вместо функций). Он реализует логику проверки регулярных выражений по умолчанию в 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__вы можете создавать более сложные объекты, используя данные экземпляра и другие методы для делегирования повторяющихся задач и т. д. это все еще можно назвать.кроме того, иногда вы используете оба объекта для сложных задач (где имеет смысл писать выделенный класс) и объекты для простых задач (которые уже существуют в функциях или более легко записываются как функции). Чтобы иметь общий интерфейс, вам либо нужно написать крошечные классы, обертывающие эти функции с ожидаемым интерфейсом, либо вы сохраняете функции функций и делаете более сложные объекты вызываемыми. Давайте возьмем темы в качестве примера. Элемент
Threadобъекты из стандартного модуля библиотеки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__()который я считаю красивым. Это позволяет скрыть несколько уровней JSON/HTTP / (however_serialized) API внутри объекта.The
__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__ метод может быть использован для переопределения / повторной инициализации одного и того же объекта. Это также облегчает использование экземпляров / объектов класса в качестве функций, передавая аргументы объектам.
Comments