168

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

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

4

14 回答 14

129

此示例使用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

它也是有状态的

于 2011-04-28T20:56:34.367 回答
92

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 这样的大型框架来说,它的可读性或可扩展性都不是那么好。

于 2011-04-28T23:33:48.793 回答
41

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

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

于 2011-04-28T21:05:21.313 回答
34

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

程序输出:

Entering hello
p1= foo bar
Hello
Leaving hello
于 2014-09-15T09:08:47.920 回答
11

是的,当您知道您正在处理对象时,完全有可能(并且在许多情况下是可取的)使用显式方法调用。但是,有时您处理需要可调用对象的代码 - 通常是函数,但由于__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__代码稍微不那么明显,并且不会增加任何价值。

于 2011-04-28T21:06:04.160 回答
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上对各种选项有很好的描述

于 2011-04-28T21:12:11.153 回答
4

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

于 2012-11-02T16:23:16.233 回答
4

我刚刚偶然发现了一个我认为很漂亮的__call__()in Concert的用法。__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

编辑:更新了指向主版本的链接。

于 2016-03-22T15:22:50.910 回答
1

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

于 2013-09-12T13:34:31.527 回答
1

我们可以使用__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")             
于 2016-12-05T08:21:55.327 回答
1

一个常见的例子是__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
于 2018-02-24T15:20:43.213 回答
1

我发现一个使用可调用对象的好地方,那些定义__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']
于 2019-01-27T16:39:29.417 回答
1

这为时已晚,但我正在举一个例子。想象一下,你有一个Vector班级和一个Point班级。两者都x, y作为位置参数。假设您想创建一个函数来移动要放在向量上的点。

4 解决方案

  • put_point_on_vec(point, vec)

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

  • 使它成为Point类的方法。my_point.put_on_vec(vec)
  • Vector实现__call__,所以你可以像使用它一样my_vec_instance(point)

这实际上是我正在为使用数学解释的 dunder 方法指南编写的一些示例的一部分,我迟早会发布。

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

于 2020-03-31T22:13:32.953 回答
0

函数调用运算符。

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

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

__call__方法可用于重新定义/重新初始化同一个对象。它还通过将参数传递给对象来促进将类的实例/对象用作函数。

于 2018-09-12T09:30:03.407 回答