Практический пример специального метода Python __call__

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

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

Ответ 1

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

def custom_validator(value):
    #your validation logic

Django имеет встроенные валидаторы по умолчанию, такие как валидаторы электронной почты, валидаторы URL и т.д., которые широко подпадают под сферу проверки подлинности RegEx. Чтобы реализовать это чисто, Django прибегает к вызываемым классам (вместо функций). Он реализует логику проверки Regex по умолчанию в 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.

Ответ 2

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

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

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

Ответ 3

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

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

Ответ 4

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

Ответ 5

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

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

Ответ 6

Декораторы на основе классов используют __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

Ответ 7

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

Ответ 8

Я просто наткнулся на использование __call__() в согласии с __getattr__(), который, я думаю, красив. Это позволяет скрыть несколько уровней API JSON/HTTP/(but_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

РЕДАКТИРОВАТЬ: Обновлена ​​ссылка, чтобы перейти к основной версии.

Ответ 9

Задайте __metaclass__ и переопределите метод __call__ и укажите, что указанные методы мета-классов __new__ возвращают экземпляр класса, альт, у вас есть "функция" с методами.

Ответ 10

Мы можем использовать метод __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")             

Ответ 11

Одним из распространенных примеров является __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

Ответ 12

Оператор вызова функции.

 class Foo: def __call__(self, a, b, c): # do something x = Foo() x(1, 2, 3)

Метод __call__ может использоваться для переопределения/повторной инициализации того же объекта. Это также облегчает использование экземпляров/объектов класса в качестве функций путем передачи аргументов объектам.

Ответ 13

Я нашел хорошее место для использования вызываемых объектов __call__(), когда используются функциональные возможности программирования в Python, такие как map(), filter(), reduce(). Например, встроенная функция Python: filter() принимает функцию и итерацию.

Лучшее время для использования вызываемого объекта над простой функцией или лямбда-функцией - когда логика сложна и должна сохранять некоторое состояние.

Вот некоторый код, который фильтрует имена файлов, используя вызываемый объект и filter().

Callable:

import os

class FileAcceptor(object):
    def __init__(self, accepted_extensions):
        self.accepted_extensions = accepted_extensions

    def __call__(self, filename):
        base, ext = os.path.splitext(filename)
        return ext in self.accepted_extensions

class ImageFileAcceptor(FileAcceptor):
    def __init__(self):
        image_extensions = ('.jpg', '.jpeg', '.gif', '.bmp')
        super(ImageFileAcceptor, self).__init__(image_extensions)

Использование:

filenames = [
    'me.jpg',
    'me.txt',
    'friend1.jpg',
    'friend2.bmp',
    'you.jpeg',
    'you.xml']

acceptor = ImageFileAcceptor()
image_filenames = filter(acceptor, filenames)
print image_filenames

Выход:

['me.jpg', 'friend1.jpg', 'friend2.bmp', 'you.jpeg']