Python __call__特殊方法的实际例子

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

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

Django使用__call__方法很好地实现了表单validation的一致API。 您可以在Django中为您的表单编写您自己的validation器。

 def custom_validator(value): #your validation logic 

Django有一些默认的内置validation器,比如电子邮件validation器,urlvalidation器等,这些validation器广泛属于RegExvalidation器的范畴。 为了实现这些干净,Django诉诸可调用类(而不是函数)。 它在RegexValidator中实现默认的正则expression式validation逻辑,然后扩展这些类用于其他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这样的大型框架可读或不易扩展。

这个例子使用memoization ,基本上把值存储在一个表(在这个例子中是字典),所以你可以查看它们,而不是重新计算它们。

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

它也是有状态的。

我觉得它很有用,因为它允许我创build易于使用的API(您有一些需要某些特定参数的可调用对象),并且易于实现,因为您可以使用面向对象的实践。

以下是我昨天写的代码,它使得散列整个文件而不是string的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') 

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

__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__你可以构build更复杂的对象,实例数据和更多的方法来委托重复任务等仍然可调用。

另外,有时候你正在使用两个对象来完成复杂的任务(编写一个专用类的时候是有意义的)和简单任务的对象(已经存在于函数中,或者更容易写成函数)。 要有一个通用的接口,你必须编写包含这些函数的小类,并使用期望的接口,或者保留函数的function,使更复杂的对象可调用。 我们以线程为例。 来自标准库文件模块threadingThread对象需要一个可调用的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) 

这仅仅是我头顶的一个例子,但是我认为已经足够复杂了。 这样做只有function是很难的,至less它需要返回两个function,这是慢慢复杂。 我们可以__call__重命名为其他内容,并传递一个绑定的方法,但是这使得创build线程的代码稍微不明显,并且不会添加任何值。

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

恕我直言__call__方法和闭__call__我们一个自然的方式来创buildPython中的STRATEGYdevise模式。 我们定义了一系列algorithm,封装每一个algorithm,使它们可以互换,最后我们可以执行一组通用的步骤,例如,计算一个文件的散列。

我只是偶然发现了__call__()__getattr__()搭配,我认为这很美。 它允许你隐藏一个对象内的多个JSON / HTTP /(whatever_serialized)API级别。

__getattr__()部分负责迭代地返回同一个类的修改后的实例,每次只填写一个属性。 然后,在所有的信息已经用尽之后, __call__()接pipe你传入的任何参数。

使用这个模型,你可以例如进行一个像api.v2.volumes.ssd.update(size=20)的调用,这个调用以https://some.tld/api/v2/volumes/ssd/update的PUT请求https://some.tld/api/v2/volumes/ssd/update

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

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

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

我们可以使用__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")