Python __call__ специальный метод практический пример - PullRequest
147 голосов
/ 29 апреля 2011

Я знаю, что __call__ метод в классе запускается, когда вызывается экземпляр класса. Однако я понятия не имею, когда я смогу использовать этот специальный метод, поскольку можно просто создать новый метод и выполнить ту же операцию, что и в методе __call__, и вместо вызова экземпляра вы можете вызвать метод.

Я был бы очень признателен, если бы кто-нибудь дал мне практическое применение этого специального метода.

Ответы [ 13 ]

115 голосов
/ 29 апреля 2011

В этом примере используется memoization , в основном сохраняющее значения в таблице (в данном случае словарь), чтобы вы могли искать их позже, а не пересчитывать.

Здесь мы используем простой классс __call__ методом для вычисления факториалов (через вызываемый объект ) вместо факториальной функции, которая содержит статическую переменную (как это невозможно в 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

И также с состоянием.

84 голосов
/ 29 апреля 2011

Модуль форм Django прекрасно использует метод __call__ для реализации согласованного API для проверки формы. Вы можете написать свой собственный валидатор для формы в Django как функцию.

def custom_validator(value):
    #your validation logic

Django имеет несколько встроенных валидаторов по умолчанию, таких как валидаторы электронной почты, валидаторы URL и т. Д., Которые в целом входят в зону валидаторов RegEx. Чтобы реализовать их полностью, Django прибегает к вызываемым классам (вместо функций). Он реализует логику проверки по умолчанию в 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 аналогична тому, что другие объяснили в своих ответах ниже. Может ли это быть реализовано любым другим способом? Вы могли бы, но ИМХО, это не будет так же легко читаемо или так легко расширяемо для больших фреймворков, как Django.

38 голосов
/ 29 апреля 2011

Я считаю его полезным, поскольку он позволяет мне создавать 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 functions:

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

Конечно, я мог бы реализовать это по-другому, но в этом случае это казалось простым подходом.

20 голосов
/ 15 сентября 2014

__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()
9 голосов
/ 29 апреля 2011

Да, когда вы знаете, что имеете дело с объектами, вполне возможно (и во многих случаях желательно) использовать явный вызов метода.Однако иногда вы имеете дело с кодом, который ожидает вызываемые объекты - как правило, функции, но благодаря __call__ вы можете создавать более сложные объекты с данными экземпляра и большим количеством методов для делегирования повторяющихся задач и т. Д., Которые все еще могут вызываться.

Кроме того, иногда вы используете как объекты для сложных задач (где имеет смысл написать выделенный класс), так и объекты для простых задач (которые уже существуют в функциях или легче записываются как функции).Чтобы иметь общий интерфейс, вы должны либо написать крошечные классы, обертывающие эти функции ожидаемым интерфейсом, либо оставить функции функций и сделать более сложные объекты вызываемыми.Давайте возьмем темы в качестве примера.Объекты 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__ во что-то другое и передать связанный метод, но это делает код, создающий поток, немного менее очевидным и не добавляет никакого значения.

5 голосов
/ 29 апреля 2011

Декораторы на основе классов используют __call__ для ссылки на упакованную функцию. E.g.:

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

3 голосов
/ 22 марта 2016

Я только что наткнулся на использование __call__() в сочетании с __getattr__(), что я считаю прекрасным. Он позволяет скрыть несколько уровней API JSON / HTTP / (однако_serialized) внутри объекта.

Часть __getattr__() обеспечивает итеративный возврат измененного экземпляра того же класса, заполняя еще один атрибут за раз. Затем, после исчерпания всей информации, __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

РЕДАКТИРОВАТЬ: Обновлена ​​ссылка для указания на главную ревизию.

3 голосов
/ 02 ноября 2012

ИМХО __call__ метод и замыкания дают нам естественный способ создания шаблона проектирования STRATEGY в Python. Мы определяем семейство алгоритмов, инкапсулируем каждый из них, делаем их взаимозаменяемыми и, в конце концов, мы можем выполнить общий набор шагов и, например, вычислить хеш для файла.

1 голос
/ 05 декабря 2016

Мы можем использовать метод __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")             
1 голос
/ 12 сентября 2013

Укажите __metaclass__ и переопределите метод __call__, и у заданного мета-класса метод __new__ вернет экземпляр класса, альт у вас есть "функция" с методами.

...