使用元类的 __call__ 方法而不是 __new__?
在讨论元类时,文档指出:
您当然也可以重写其他类方法(或添加新的 方法);例如,在 元类允许在调用类时自定义行为,例如不 始终创建一个新实例。
[编者注:这已从 3.3 的文档中删除。在 3.2 中:自定义类创建]
我的问题是:假设我想在调用类时具有自定义行为,例如缓存而不是创建新对象。我可以通过重写类的 __new__ 方法来做到这一点。我什么时候想用 __call__
定义元类?这种方法提供了哪些 __new__
无法实现的功能?
When discussing metaclasses, the docs state:
You can of course also override other class methods (or add new
methods); for example defining a custom__call__()
method in the
metaclass allows custom behavior when the class is called, e.g. not
always creating a new instance.
[Editor's note: This was removed from the docs in 3.3. It's here in 3.2: Customizing class creation]
My questions is: suppose I want to have custom behavior when the class is called, for example caching instead of creating fresh objects. I can do this by overriding the __new__
method of the class. When would I want to define a metaclass with __call__
instead? What does this approach give that isn't achievable with __new__
?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
当您仔细观察这些方法的执行顺序时,细微的差异会变得更加明显。
请注意,上面的代码除了记录我们正在做的事情之外,实际上并没有做任何事情。每个方法都遵循其父实现,即其默认实现。因此,除了记录之外,它实际上就像您简单地声明了以下内容:
现在让我们创建一个
Class_1
的实例因此,如果
type
是Meta_1
的父级code> 我们可以想象一下type.__call__()
的伪实现:从上面的调用顺序注意到
Meta_1.__call__()
(或者在本例中type.__call__()
) 有机会影响是否调用Class_1.__new__()
和Class_1.__init__()
最终做成了。在其执行过程中,Meta_1.__call__()
可能会返回一个甚至都没有被触及的对象。以单例模式的这种方法为例:让我们观察重复尝试创建
Class_2
类型的对象时会发生什么现在使用类的
__new__()
方法观察此实现尝试完成同样的事情。请注意,上述实现即使在类上成功注册了单例,也不会阻止调用
__init__()
,这种情况在type.__call__()
中隐式发生 (<如果未指定,则 code>type 为默认元类)。这可能会导致一些不良影响:The subtle differences become a bit more visible when you carefully observe the execution order of these methods.
Note that the code above doesn't actually do anything other than log what we're doing. Each method defers to its parent implementation i.e. its default. So beside logging it's effectively as if you had simply declared things as follows:
And now let's create an instance of
Class_1
Therefore if
type
is the parent ofMeta_1
we can imagine a pseudo implementation oftype.__call__()
as such:Notice from the call order above that
Meta_1.__call__()
(or in this casetype.__call__()
) is given the opportunity to influence whether or not calls toClass_1.__new__()
andClass_1.__init__()
are eventually made. Over the course of its executionMeta_1.__call__()
could return an object that hasn't even been touched by either. Take for example this approach to the singleton pattern:Let's observe what happens when repeatedly trying to create an object of type
Class_2
Now observe this implementation using a class'
__new__()
method to try to accomplish the same thing.Notice that the above implementation even though successfully registering a singleton on the class, does not prevent
__init__()
from being called, this happens implicitly intype.__call__()
(type
being the default metaclass if none is specified). This could lead to some undesired effects:您的问题的直接答案是:当您想要做更多而不仅仅是自定义实例创建时,或者当您想要将类做什么与其创建方式分开时。
请参阅我对 Creating a singleton in Python 的回答以及相关讨论。
有几个优点。
它允许您将类的功能与其创建的详细信息分开。元类和类各自负责一件事。
您可以在元类中编写一次代码,并使用它来自定义多个类的调用行为,而不必担心多重继承。
子类可以重写其
__new__
方法中的行为,但元类上的__call__
甚至根本不必调用__new__
。< /p>如果有设置工作,您可以在元类的 __new__ 方法中完成,并且只发生一次,而不是每次调用该类时都会发生。
当然,在很多情况下,如果您不担心单一职责原则,那么自定义
__new__
也同样有效。但还有其他用例必须在创建类时(而不是创建实例时)更早发生。当这些发挥作用时,元类就变得必要了。请参阅 元类的(具体)用例是什么在 Python 中? 有很多很棒的例子。
The direct answer to your question is: when you want to do more than just customize instance creation, or when you want to separate what the class does from how it's created.
See my answer to Creating a singleton in Python and the associated discussion.
There are several advantages.
It allows you to separate what the class does from the details of how it's created. The metaclass and class are each responsible for one thing.
You can write the code once in a metaclass, and use it for customizing several classes' call behavior without worrying about multiple inheritance.
Subclasses can override behavior in their
__new__
method, but__call__
on a metaclass doesn't have to even call__new__
at all.If there is setup work, you can do it in the
__new__
method of the metaclass, and it only happens once, instead of every time the class is called.There are certainly lots of cases where customizing
__new__
works just as well if you're not worried about the single responsibility principle.But there are other use cases that have to happen earlier, when the class is created, rather than when the instance is created. It's when these come in to play that a metaclass is necessary. See What are your (concrete) use-cases for metaclasses in Python? for lots of great examples.
一个区别是,通过定义元类
__call__
方法,您要求在任何类或子类的__new__
方法有机会被调用之前调用它。请注意,
SubFoo.__new__
永远不会被调用。相反,如果您在没有元类的情况下定义Foo.__new__
,则允许子类覆盖Foo.__new__
。当然,您可以定义
MetaFoo.__call__
来调用cls.__new__
,但这取决于您。通过拒绝这样做,您可以阻止子类调用其__new__
方法。我认为在这里使用元类没有明显的优势。由于“简单胜于复杂”,我建议使用 __new__ 。
One difference is that by defining a metaclass
__call__
method you are demanding that it gets called before any of the class's or subclasses's__new__
methods get an opportunity to be called.Notice that
SubFoo.__new__
never gets called. In contrast, if you defineFoo.__new__
without a metaclass, you allow subclasses to overrideFoo.__new__
.Of course, you could define
MetaFoo.__call__
to callcls.__new__
, but that's up to you. By refusing to do so, you can prevent subclasses from having their__new__
method called.I don't see a compelling advantage to using a metaclass here. And since "Simple is better than complex", I'd recommend using
__new__
.我认为 Pyrscope 的答案的充实的 Python 3 版本对于某人复制、粘贴和破解可能会很方便(可能是我,当我发现自己在 6 个月内再次回到这个页面查找它时)。它取自这篇文章:
输出:
另一个很棒的资源同一篇文章重点介绍的是 David Beazley 的 PyCon 2013 Python 3 元编程教程。
I thought a fleshed out Python 3 version of pyroscope's answer might be handy for someone to copy, paste and hack about with (probably me, when I find myself back at this page looking it up again in 6 months). It is taken from this article:
Outputs:
Another great resource highlighted by the same article is David Beazley's PyCon 2013 Python 3 Metaprogramming tutorial.
这是生命周期阶段以及您可以访问的内容的问题。
__call__
在__new__
之后被调用,并在初始化参数之前被传递给__init__< /code>,这样你就可以操纵它们。尝试这段代码并研究它的输出:
It's a matter of lifecycle phases and what you have access to.
__call__
gets called after__new__
and is passed the initialization parameters before they get passed on to__init__
, so you can manipulate them. Try this code and study its output:在问题中给出的特定示例中,覆盖元类中的
__call__
优于覆盖类中的__new__
。如果缓存的目的是提高效率,则缓存
__new__
'结果不是最佳,因为__init__
无论如何都会执行(数据模型:基本自定义)。例如:缓存
__new__
的结果是只有当__init__
对已经初始化的实例没有明显影响时才有效。 否则在缓存对象上执行__init__
可能会导致对其其他引用产生令人讨厌的副作用。元类级别的缓存既避免了1.的性能问题,也避免了2.的正确性问题。例如:
请注意,Michael Ekoka 的回答已经提到了由于重复
__init__
执行而可能出现的不良影响在覆盖__new__
方法中(如我的第 2 项)。In the particular example given in the question, overriding
__call__
in the metaclass is just superior to overriding__new__
in the class.If the purpose of caching is efficiency, then caching
__new__
's result is not optimal because__init__
is executed anyhow (Data Model: Basic Customization). For example:Caching
__new__
's result is sound only if__init__
has no noticeable effect on an already initialized instance. Otherwise the execution of__init__
on a cached object could lead to annoying side-effects on its other references.Caching at the metaclass level avoids both the performance issue of 1. and the correctness issue of 2. For example:
Note that Michael Ekoka's answer already mention undesired effects that may arise due to repeated
__init__
execution within the override__new__
approach (as in my item 2).