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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(16)
此示例使用 memoization,基本上将值存储在表中(在本例中为字典),以便您可以查看稍后再更新,而不是重新计算。
这里我们使用一个带有
__call__
方法的简单类来计算阶乘(通过可调用对象)而不是包含静态变量的阶乘函数(因为这在 Python 中是不可能的)。现在您有了一个可调用的
fact
对象,就像所有其他函数一样。例如并且它也是有状态的。
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).Now you have a
fact
object which is callable, just like every other function. For exampleAnd it is also stateful.
Django 表单模块很好地使用了 __call__ 方法来实现一致的表单验证 API。您可以为 Django 中的表单编写自己的验证器作为函数。
Django 有一些默认的内置验证器,例如电子邮件验证器、url 验证器等,它们大致属于 RegEx 验证器的范畴。为了干净地实现这些,Django 求助于可调用类(而不是函数)。它在 RegexValidator 中实现默认的 Regex 验证逻辑,然后扩展这些类以进行其他验证。
现在,您的自定义函数和内置 EmailValidator 都可以使用相同的语法进行调用。
正如您所看到的,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.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.
Now both your custom function and built-in EmailValidator can be called with the same syntax.
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.
我发现它很有用,因为它允许我创建易于使用的 API(您有一些需要某些特定参数的可调用对象),并且易于实现,因为您可以使用面向对象的实践。
以下是我昨天编写的代码,它生成了 hashlib.foo 方法的一个版本,该方法对整个文件而不是字符串进行哈希处理:
此实现允许我以与 hashlib 类似的方式使用这些函数.foo 函数:
当然我可以用不同的方式实现它,但在这种情况下,它似乎是一个简单的方法。
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:This implementation allows me to use the functions in a similar fashion to the
hashlib.foo
functions:Of course I could have implemented it a different way, but in this case it seemed like a simple approach.
__call__
还用于在 python 中实现装饰器类。在这种情况下,当调用带有装饰器的方法时,将调用该类的实例。程序输出:
__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.program output:
是的,当您知道自己正在处理对象时,完全有可能(并且在许多情况下建议)使用显式方法调用。然而,有时您处理的代码需要可调用对象(通常是函数),但借助 __call__ ,您可以构建更复杂的对象,使用实例数据和更多方法来委托仍然可调用的重复任务等。
此外,有时您会同时使用对象来执行复杂任务(在这种情况下编写专用类是有意义的)和对象来执行简单任务(已经存在于函数中,或者更容易编写为函数)。要拥有一个通用接口,您要么必须编写使用预期接口包装这些函数的小类,要么保留函数的功能并使更复杂的对象可调用。我们以线程为例。标准库模块
中的
想要一个可调用的Thread
对象线程目标
参数(即作为在新线程中完成的操作)。使用可调用对象,您不仅限于函数,还可以传递其他对象,例如相对复杂的工作程序,它从其他线程获取要执行的任务并按顺序执行它们:这只是我脑海中的一个示例,但我认为它已经足够复杂,足以保证上课。仅使用函数来执行此操作很困难,至少它需要返回两个函数,而且这会慢慢变得复杂。 可以将
__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 modulethreading
want a callable astarget
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: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.基于类的装饰器使用 __call__ 来引用包装的函数。例如:
Artima.com
Class-based decorators use
__call__
to reference the wrapped function. E.g.:There is a good description of the various options here at Artima.com
恕我直言,
__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.我刚刚偶然发现了 __call__() 与 __getattr__() 的配合使用,我认为这很漂亮。它允许您在对象内隐藏多个级别的 JSON/HTTP/(however_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
编辑:更新了链接以指向主修订版。
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 tohttps://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.
我发现使用可调用对象的好地方,即那些定义 __call__() 的对象,是在使用 Python 中的函数式编程功能时,例如
map()
、过滤器(),减少()。
在普通函数或 lambda 函数上使用可调用对象的最佳时机是当逻辑复杂并且需要保留某些状态或使用未传递给 __call__() 函数的其他信息时。
下面是一些使用可调用对象和
filter()
根据文件扩展名过滤文件名的代码。可调用:
用法:
输出:
I find a good place to use callable objects, those that define
__call__()
, is when using the functional programming capabilities in Python, such asmap()
,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:
Usage:
Output:
我是新手,但我的看法是:使用
__call__
使组合更容易编码。如果f, g
是具有方法eval(self,x)
的类Function
的实例,则使用__call___
code> 可以写成f(g(x))
而不是f.eval(g.eval(x))
。神经网络可以由较小的神经网络组成,在 pytorch 中,我们的 Module 类中有一个 __call__ :
这是在实践中使用 __call__ 的示例:在 pytorch 中,当定义一个神经网络(例如,将其称为
class MyNN(nn.Module)
)作为 torch.nn.module.Module 的子类时,通常会定义一个forward
类的方法,但通常当将输入张量x
应用于实例model=MyNN()
时,我们只需编写model(x)
而不是model.forward(x)
但两者都给出相同的答案。如果您在此处深入了解 torch.nn.module.Module 的源代码https://pytorch.org/docs/stable/_modules/torch/nn/modules/module.html#Module
并搜索
__call__
最终可以追溯到 -至少在某些情况下 - 对 self.forward 的调用将在最后两行打印相同的值,因为 Module 类已经实现了 __call___ 所以这就是我相信的 Python当它看到
model(x)
和__call__
时转向,最终调用model.forward(x))
I'm a novice, but here is my take: having
__call__
makes composition easier to code. Iff, g
are instance of a classFunction
that has a methodeval(self,x)
, then with__call___
one could writef(g(x))
as opposed tof.eval(g.eval(x))
.A neural network can be composed from smaller neural networks, and in pytorch we have a
__call__
in the Module class:Here's an example of where
__call__
is used in practice: in pytorch, when defining a neural network (call itclass MyNN(nn.Module)
for example) as a subclass of torch.nn.module.Module, one typically defines aforward
method for the class, but typically when applying an input tensorx
to an instancemodel=MyNN()
we just writemodel(x)
as opposed tomodel.forward(x)
but both give the same answer. If you dig into the source for torch.nn.module.Module herehttps://pytorch.org/docs/stable/_modules/torch/nn/modules/module.html#Module
and search for
__call__
one can eventually trace it back - at least in some cases - to a call toself.forward
will print the same values in the last two lines as the Module class has implemented
__call___
so that's what I believe Python turns to when it seesmodel(x)
, and the__call__
which in turn eventually callsmodel.forward(x))
指定一个
__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.我们可以使用
__call__
方法将其他类方法用作静态方法。We can use
__call__
method to use other class methods as static methods.一个常见的例子是 functools.partial 中的 __call__ ,这是一个简化版本(Python >= 3.5):
用法:
One common example is the
__call__
infunctools.partial
, here is a simplified version (with Python >= 3.5):Usage:
这已经太晚了,但我举了一个例子。假设您有一个 Vector 类和 Point 类。两者都采用 x, y 作为位置参数。假设您想要创建一个函数来移动要放在向量上的点。
4 解决方案
put_point_on_vec(point, vec)
使其成为向量类上的方法。 eg
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 aPoint
class. Both takex, 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)
Point
class.my_point.put_on_vec(vec)
Vector
implements__call__
, So you can use it likemy_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
函数调用运算符。
__call__ 方法可用于重新定义/重新初始化同一对象。它还通过将参数传递给对象来促进将类的实例/对象用作函数。
The function call operator.
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.
现在输出可以是..(因为前两个答案不断在 [0,3] 之间移动)
2
1
True
您可以看到 Class Bingo 实现了 _call_ 方法,这是创建类似函数的简单方法具有必须在调用之间保留的内部状态的对象,例如 Bingo 中的剩余项目。 _call_ 的另一个很好的用例是装饰器。装饰器必须是可调用的,有时“记住”很方便
装饰器调用之间的一些东西。 (即,记忆化 - 缓存昂贵的计算结果以供以后使用。)
Now the output can be..(As first two answer keeps shuffling between [0,3])
2
1
True
You can see that Class Bingo implements _call_ method, an easy way to create a function like objects that have an internal state that must be kept across invocations like remaining items in Bingo. Another good use case of _call_ is Decorators. Decorators must be callable and it is sometimes convenient to "remember"
something between calls of decorators. (i.e., memoization- caching the results of expensive computation for later use.)