Python __call__特殊方法实用例子

moh*_*666 150 python methods call magic-methods

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

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

S.L*_*ott 116

此示例使用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()
Run Code Online (Sandbox Code Playgroud)

现在你有一个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
Run Code Online (Sandbox Code Playgroud)

而且它也是状态.

  • @delnan:这不是最短的.没人赢得代码高尔夫.它旨在显示`__call__`,简单而且仅此而已. (8认同)
  • @delnan - 几乎任何事情都可以通过几种不同的方式完成.哪一个更具可读性取决于读者. (4认同)
  • 但是,当演示的技术不适合任务时,它会破坏这个例子,不是吗?(而且我并不是关于"让我们保留它的线条" - 短暂的,我正在谈论"以同样清晰的方式写它并保存一些样板代码" - 短时间.请放心,我不是其中一个疯子试图写出可能的最短代码,我只是想避免使用样板代码为读者添加任何内容.) (3认同)
  • 我宁愿有一个可以索引的`fact`对象,因为你的`__call__`函数本质上是一个索引.也会使用列表而不是字典,但那只是我. (2认同)

Pra*_*ota 86

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

def custom_validator(value):
    #your validation logic
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

现在,可以使用相同的语法调用自定义函数和内置EmailValidator.

for v in [custom_validator, EmailValidator()]:
    v(value) # <-----
Run Code Online (Sandbox Code Playgroud)

如您所见,Django中的这种实现类似于其他人在下面的答案中解释的内容.这可以用其他方式实现吗?你可以,但恕我直言,对于像Django这样的大型框架,它不具有可读性或易于扩展性.

  • 这是*它是如何使用的一个例子,但在我看来并不是一个好的例子.在这种情况下,拥有一个可调用的实例是没有优势的.最好有一个带有方法的接口/抽象类,比如.validate(); 只有更明确的是同样的事情.\ _\_ _ call__的真正价值是能够在需要可调用的地方使用实例.例如,在创建装饰器时,我经常使用\ _\_ _ call__. (14认同)
  • 因此,如果使用正确,它可以使代码更具可读性.我假设如果它在错误的地方使用,它将使代码也非常难以理解. (5认同)

bra*_*ers 39

我发现它很有用,因为它允许我创建易于使用的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)
Run Code Online (Sandbox Code Playgroud)

这个实现允许我以类似的方式使用hashlib.foo函数:

from filehash import sha1
print sha1('somefile.txt')
Run Code Online (Sandbox Code Playgroud)

当然我可以用不同的方式实现它,但在这种情况下,它看起来像一个简单的方法.

  • 我不相信它总是*对某人(例如可能只使用过Java的人)一样清楚*.嵌套函数和变量查找/范围可能会令人困惑.我想我的观点是`__call__`为您提供了一个工具,可以让您使用OO技术来解决问题. (8认同)
  • 在您处理多处理模块时,您更愿意使用`__call__`方法而不是闭包的示例,该模块使用酸洗在进程之间传递信息.你不能腌制一个闭包,但你可以挑选一个类的实例. (8认同)
  • 同样,闭包毁了这个例子.http://pastebin.com/961vU0ay是80%的线条,同样清晰. (7认同)
  • 我认为当两者都提供相同的功能时,"为什么使用X而Y"的问题是非常主观的.对于某些人来说,OO方法更容易理解,对于其他人来说,关闭方法是.除非你有*使用``isinstance``或类似的东西,否则没有令人信服的论据可以使用一个而不是另一个. (4认同)
  • @delnan您的闭包示例代码行数较少,但它更加难以争辩. (2认同)
  • 关闭方法也丧失了继承的所有好处. (2认同)

Kri*_*ris 21

__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()
Run Code Online (Sandbox Code Playgroud)


小智 9

是的,当您知道自己正在处理对象时,使用显式方法调用是完全可能的(并且在许多情况下是可取的).但是,有时您处理需要可调用对象的代码 - 通常是函数,但是由于__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)
Run Code Online (Sandbox Code Playgroud)

这只是我头脑中的一个例子,但我认为它已经足够复杂以保证课程.仅使用函数执行此操作很难,至少需要返回两个函数,而且这些函数正在慢慢变得复杂.一个可以重命名__call__到别的东西,并通过绑定方法,但是这使得代码稍微不太明显的创建线程,并且不会增加任何价值.

  • 在这里使用短语"duck typing"(http://en.wikipedia.org/wiki/Duck_typing#In_Python)可能很有用 - 您可以通过这种方式使用更复杂的类对象来模仿*函数. (3认同)
  • 作为一个相关的例子,我看到`__call__`用于使用类实例(而不是函数)作为WSGI应用程序.以下是"塔的权威指南"中的一个例子:[使用类的实例](http://pylonsbook.com/en/1.1/the-web-server-gateway-interface-wsgi.html#using-instances-of - 班) (2认同)

ror*_*ycl 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)
Run Code Online (Sandbox Code Playgroud)

Artima.com上有各种选项的很好的描述