Python __call__特殊方法实用例子

时间:2011-04-28 20:54:22

标签: python methods call magic-methods

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

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

14 个答案:

答案 0 :(得分:114)

此示例使用memoization,基本上将值存储在表中(在本例中为字典),以便稍后查找它们而不是重新计算它们。

这里我们使用一个带有__call__方法的简单类来计算阶乘(通过callable object)而不是包含静态变量的阶乘函数(因为这在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

它也是有状态的。

答案 1 :(得分:83)

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

def custom_validator(value):
    #your validation logic

Django有一些默认的内置验证器,如电子邮件验证器,URL验证器等,它们大致属于RegEx验证器的范畴。为了干净利落地实现这些,Django转向可调用类(而不是函数)。它在RegexValidator中实现默认的Regex Validation逻辑,然后扩展这些类以进行其他验证。

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这样的大型框架来说,它不具有可读性或易于扩展性。

答案 2 :(得分:38)

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

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

答案 3 :(得分:20)

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

答案 4 :(得分:9)

是的,当你知道你正在处理对象时,使用显式方法调用是完全可能的(并且在许多情况下是可取的)。但是,有时您处理需要可调用对象的代码 - 通常是函数,但是由于__call__,您可以构建更复杂的对象,使用实例数据和更多方法来委派仍然可调用的重复任务等。

此外,有时您将两个对象用于复杂任务(编写专用类有意义)和简单任务对象(已存在于函数中,或更容易编写为函数)。要拥有一个通用接口,您必须编写包含具有预期接口的那些函数的小类,或者保留函数函数并使更复杂的对象可调用。我们以线程为例。 Thread objects from the standard libary module 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 :(得分:5)

基于类的装饰器使用__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

对各种选项有很好的描述

答案 6 :(得分:3)

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

答案 7 :(得分:3)

我偶然发现了__call__()__getattr__()一致的用法,我觉得这很美。它允许您在对象内隐藏多个级别的JSON / HTTP /(except_serialized)API。

__getattr__()部分负责迭代地返回同一个类的修改实例,一次填充一个属性。然后,在所有信息都用尽之后,__call__()将接管您传入的任何参数。

使用此模型,您可以进行类似api.v2.volumes.ssd.update(size=20)的调用,该调用最终会发送到https://some.tld/api/v2/volumes/ssd/update的PUT请求。

特定代码是OpenStack中某个卷后端的块存储驱动程序,您可以在此处查看:https://github.com/openstack/cinder/blob/master/cinder/volume/drivers/nexenta/jsonrpc.py

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

答案 8 :(得分:1)

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

答案 9 :(得分: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")             

答案 10 :(得分:0)

一个常见示例是__call__中的functools.partial,这是一个简化版本(Python&gt; = 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

答案 11 :(得分:0)

函数调用运算符。

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

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

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

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

答案 12 :(得分:0)

在使用Python中的函数式编程功能(例如__call__()map(),{{1})时,我找到了使用可调用对象(定义{{​​1}}的对象)的好地方}。

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

>

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

可致电:

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

答案 13 :(得分:0)

这为时已晚,但我举一个例子。假设您有一个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方法的指南,我迟早会发布。

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