Python __call__特殊方法的实际示例

问题:Python __call__特殊方法的实际示例

我知道__call__调用类的实例时会触发类中的方法。但是,我不知道何时可以使用这种特殊方法,因为一个人可以简单地创建一个新方法并执行在__call__方法中完成的相同操作,而无需调用实例,而可以调用该方法。

如果有人给我这种特殊方法的实际用法,我将不胜感激。

I know that __call__ method in a class is triggered when the instance of a class is called. However, I have no idea when I can use this special method, because one can simply create a new method and perform the same operation done in __call__ method and instead of calling the instance, you can call the method.

I would really appreciate it if someone gives me a practical usage of this special method.


回答 0

Django表单模块__call__很好地使用了方法来实现用于表单验证的一致API。您可以在Django中将自己的验证器作为函数编写。

def custom_validator(value):
    #your validation logic

Django有一些默认的内置验证器,例如电子邮件验证器,URL验证器等,它们大体上属于RegEx验证器。为了清晰地实现这些,Django求助于可调用类(而不是函数)。它在RegexValidator中实现默认的Regex验证逻辑,然后扩展这些类以进行其他验证。

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这样的大型框架,它不那么易读或易于扩展。

Django forms module uses __call__ method nicely to implement a consistent API for form validation. You can write your own validator for a form in Django as a function.

def custom_validator(value):
    #your validation logic

Django has some default built-in validators such as email validators, url validators etc., which broadly fall under the umbrella of RegEx validators. To implement these cleanly, Django resorts to callable classes (instead of functions). It implements default Regex Validation logic in a RegexValidator and then extends these classes for other validations.

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

Now both your custom function and built-in EmailValidator can be called with the same syntax.

for v in [custom_validator, EmailValidator()]:
    v(value) # <-----

As you can see, this implementation in Django is similar to what others have explained in their answers below. Can this be implemented in any other way? You could, but IMHO it will not be as readable or as easily extensible for a big framework like Django.


回答 1

本示例使用备忘录,基本上将值存储在表(在这种情况下为字典)中,因此您以后可以查找它们,而无需重新计算它们。

在这里,我们使用带有__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

而且它也是有状态的。

This example uses memoization, basically storing values in a table (dictionary in this case) so you can look them up later instead of recalculating them.

Here we use a simple class with a __call__ method to calculate factorials (through a callable object) instead of a factorial function that contains a static variable (as that’s not possible in 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()

Now you have a fact object which is callable, just like every other function. For example

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

And it is also stateful.


回答 2

我发现它很有用,因为它允许我创建易于使用的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')

当然,我可以用其他方式实现它,但是在这种情况下,这似乎是一种简单的方法。

I find it useful because it allows me to create APIs that are easy to use (you have some callable object that requires some specific arguments), and are easy to implement because you can use Object Oriented practices.

The following is code I wrote yesterday that makes a version of the hashlib.foo methods that hash entire files rather than strings:

# 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)

This implementation allows me to use the functions in a similar fashion to the hashlib.foo functions:

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

Of course I could have implemented it a different way, but in this case it seemed like a simple approach.


回答 3

__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__ is also used to implement decorator classes in python. In this case the instance of the class is called when the method with the decorator is called.

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

回答 4

是的,当您知道要处理对象时,完全有可能(在很多情况下建议使用)显式方法调用。但是,有时您要处理期望可调用对象的代码-通常是函数,但是由于__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__到别的东西,并通过绑定方法,但是这使得代码稍微不太明显的创建线程,并且不会增加任何价值。

Yes, when you know you’re dealing with objects, it’s perfectly possible (and in many cases advisable) to use an explicit method call. However, sometimes you deal with code that expects callable objects – typically functions, but thanks to __call__ you can build more complex objects, with instance data and more methods to delegate repetitive tasks, etc. that are still callable.

Also, sometimes you’re using both objects for complex tasks (where it makes sense to write a dedicated class) and objects for simple tasks (that already exist in functions, or are more easily written as functions). To have a common interface, you either have to write tiny classes wrapping those functions with the expected interface, or you keep the functions functions and make the more complex objects callable. Let’s take threads as example. The Thread objects from the standard libary module threading want a callable as target argument (i.e. as action to be done in the new thread). With a callable object, you are not restricted to functions, you can pass other objects as well, such as a relatively complex worker that gets tasks to do from other threads and executes them sequentially:

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)

This is just an example off the top of my head, but I think it is already complex enough to warrant the class. Doing this only with functions is hard, at least it requires returning two functions and that’s slowly getting complex. One could rename __call__ to something else and pass a bound method, but that makes the code creating the thread slightly less obvious, and doesn’t add any value.


回答 5

基于类的装饰器__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上的各种选项都有很好的描述

Class-based decorators use __call__ to reference the wrapped function. 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)

There is a good description of the various options here at Artima.com


回答 6

IMHO __call__方法和闭包为我们提供了一种在Python中创建STRATEGY设计模式的自然方法。我们定义了一系列算法,将每个算法封装在一起,使其可互换,最后我们可以执行一组通用步骤,例如,计算文件的哈希值。

IMHO __call__ method and closures give us a natural way to create STRATEGY design pattern in Python. We define a family of algorithms, encapsulate each one, make them interchangeable and in the end we can execute a common set of steps and, for example, calculate a hash for a file.


回答 7

我只是偶然发现了一种我认为很美的__call__()音乐会__getattr__()。它允许您在对象内部隐藏JSON / HTTP /(however_serialized)API的多个级别。

__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

编辑:更新了链接以指向主修订。

I just stumbled upon a usage of __call__() in concert with __getattr__() which I think is beautiful. It allows you to hide multiple levels of a JSON/HTTP/(however_serialized) API inside an object.

The __getattr__() part takes care of iteratively returning a modified instance of the same class, filling in one more attribute at a time. Then, after all information has been exhausted, __call__() takes over with whatever arguments you passed in.

Using this model, you can for example make a call like api.v2.volumes.ssd.update(size=20), which ends up in a PUT request to https://some.tld/api/v2/volumes/ssd/update.

The particular code is a block storage driver for a certain volume backend in OpenStack, you can check it out here: https://github.com/openstack/cinder/blob/master/cinder/volume/drivers/nexenta/jsonrpc.py

EDIT: Updated the link to point to master revision.


回答 8

指定一个__metaclass__并覆盖该__call__方法,并让指定的元类的__new__方法返回该类的实例,中提琴表示您具有带有方法的“函数”。

Specify a __metaclass__ and override the __call__ method, and have the specified meta classes’ __new__ method return an instance of the class, viola you have a “function” with methods.


回答 9

我们可以使用__call__method来将其他类方法用作静态方法。

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

We can use __call__ method to use other class methods as static methods.

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

回答 10

一个常见的示例是__call__in 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

One common example is the __call__ in functools.partial, here is a simplified version (with 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)

Usage:

def add(x, y):
    return x + y

inc = partial(add, y=1)
print(inc(41))  # 42

回答 11

函数调用运算符。

class Foo:
    def __call__(self, a, b, c):
        # do something

x = Foo()
x(1, 2, 3)

__call__方法可用于重新定义的/重新初始化相同的对象。通过将参数传递给对象,还有助于将类的实例/对象用作函数。

The function call operator.

class Foo:
    def __call__(self, a, b, c):
        # do something

x = Foo()
x(1, 2, 3)

The __call__ method can be used to redefined/re-initialize the same object. It also facilitates the use of instances/objects of a class as functions by passing arguments to the objects.


回答 12

我发现使用可调用对象的好地方,那些定义__call__(),是使用Python中的函数式编程功能时,比如map()filter()reduce()

在普通函数或lambda函数上使用可调用对象的最佳时间是逻辑复杂且需要保留某些状态或使用其他未传递给__call__()函数的信息时。

以下是一些代码,它们使用可调用对象和,根据文件名的扩展名过滤文件名filter()

可调用:

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']

I find a good place to use callable objects, those that define __call__(), is when using the functional programming capabilities in Python, such as map(), filter(), reduce().

The best time to use a callable object over a plain function or a lambda function is when the logic is complex and needs to retain some state or uses other info that in not passed to the __call__() function.

Here’s some code that filters file names based upon their filename extension using a callable object and 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)

Usage:

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

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

Output:

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

回答 13

现在为时已晚,但我举一个例子。假设您有一Vector堂课和一Point堂课。两者都x, y作为位置参数。假设您要创建一个函数来移动要放在矢量上的点。

4个解决方案

  • put_point_on_vec(point, vec)

  • 使其成为vector类的方法。例如 my_vec.put_point(point)

  • 使它成为Point该类的一个方法。my_point.put_on_vec(vec)
  • Vector工具__call__,因此您可以像my_vec_instance(point)

这实际上是我正在研究的一些示例的一部分,该指南针对用数学解释的Dunder方法指南(我迟早要发布)。

我离开了移动点本身的逻辑,因为这不是这个问题的目的

This is too late but I’m giving an example. Imagine you have a Vector class and a Point class. Both take x, y as positional args. Let’s imagine you want to create a function that moves the point to be put on the vector.

4 Solutions

  • put_point_on_vec(point, vec)

  • Make it a method on the vector class. e.g my_vec.put_point(point)

  • Make it a method on the Point class. my_point.put_on_vec(vec)
  • Vector implements __call__, So you can use it like my_vec_instance(point)

This is actually part of some examples I’m working on for a guide for dunder methods explained with Maths that I’m gonna release sooner or later.

I left the logic of moving the point itself because this is not what this question is about