使用类的 __new__ 方法作为工厂:__init__ 被调用两次 [英] Using a class' __new__ method as a Factory: __init__ gets called twice

查看:32
本文介绍了使用类的 __new__ 方法作为工厂:__init__ 被调用两次的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在python中遇到了一个奇怪的bug,使用类的__new__方法作为工厂会导致实例化类的__init__方法被调用两次.

I encountered a strange bug in python where using the __new__ method of a class as a factory would lead to the __init__ method of the instantiated class to be called twice.

这个想法最初是使用母类的 __new__ 方法根据传递的参数返回她的一个子类的特定实例,而不必在外部声明工厂函数班级.

The idea was originally to use the __new__ method of the mother class to return a specific instance of one of her children depending on the parameters that are passed, without having to declare a factory function outside of the class.

我知道使用工厂函数将是此处使用的最佳设计模式,但在项目的这个阶段更改设计模式的成本会很高.因此,我的问题是:有没有办法避免对 __init__ 的双重调用,并且在这样的模式中只对 __init__ 进行一次调用?

I know that using a factory function would be the best design-pattern to use here, but changing the design pattern at this point of the project would be costly. My question hence is: is there a way to avoid the double call to __init__ and get only a single call to __init__ in such a schema ?

class Shape(object):
    def __new__(cls, desc):
        if cls is Shape:
            if desc == 'big':   return Rectangle(desc)
            if desc == 'small': return Triangle(desc)
        else:
            return super(Shape, cls).__new__(cls, desc)

    def __init__(self, desc):
        print "init called"
        self.desc = desc

class Triangle(Shape):
    @property
    def number_of_edges(self): return 3

class Rectangle(Shape):
    @property
    def number_of_edges(self): return 4

instance = Shape('small')
print instance.number_of_edges

>>> init called
>>> init called
>>> 3

非常感谢任何帮助.

推荐答案

当你构造一个对象时,Python 调用它的 __new__ 方法来创建对象,然后调用 __init__ 上的返回的对象.当您通过调用 Triangle()__new__ 内部创建对象时,这将导致进一步调用 __new____init__>.

When you construct an object Python calls its __new__ method to create the object then calls __init__ on the object that is returned. When you create the object from inside __new__ by calling Triangle() that will result in further calls to __new__ and __init__.

你应该做的是:

class Shape(object):
    def __new__(cls, desc):
        if cls is Shape:
            if desc == 'big':   return super(Shape, cls).__new__(Rectangle)
            if desc == 'small': return super(Shape, cls).__new__(Triangle)
        else:
            return super(Shape, cls).__new__(cls, desc)

这将创建一个 RectangleTriangle 而不触发对 __init__ 的调用,然后仅调用 __init__一次.

which will create a Rectangle or Triangle without triggering a call to __init__ and then __init__ is called only once.

编辑回答@Adrian 关于超级如何工作的问题:

Edit to answer @Adrian's question about how super works:

super(Shape,cls) 搜索 cls.__mro__ 以找到 Shape 然后向下搜索序列的其余部分以找到属性.

super(Shape,cls) searches cls.__mro__ to find Shape and then searches down the remainder of the sequence to find the attribute.

Triangle.__mro__(Triangle, Shape, object)Rectangle.__mro__(Rectangle, Shape, object)Shape.__mro__ 只是 (Shape, object).对于任何这些情况,当您调用 super(Shape, cls) 时,它会忽略 mro 序列中直到并包括 Shape 的所有内容,因此唯一剩下的就是单元素元组(object,) 用于查找所需的属性.

Triangle.__mro__ is (Triangle, Shape, object) and Rectangle.__mro__ is (Rectangle, Shape, object) while Shape.__mro__ is just (Shape, object). For any of those cases when you call super(Shape, cls) it ignores everything in the mro squence up to and including Shape so the only thing left is the single element tuple (object,) and that is used to find the desired attribute.

如果您有钻石继承,这会变得更复杂:

This would get more complicated if you had a diamond inheritance:

class A(object): pass
class B(A): pass
class C(A): pass
class D(B,C): pass

现在 B 中的方法可能使用 super(B, cls) 并且如果它是 B 实例将搜索 (A, object) 但如果你有一个 D 实例 B 中的相同调用将搜索 (C, A, object) 因为 D.__mro__ 是 <代码>(B, C, A, 对象).

now a method in B might use super(B, cls) and if it were a B instance would search (A, object) but if you had a D instance the same call in B would search (C, A, object) because the D.__mro__ is (B, C, A, object).

因此,在这种特殊情况下,您可以定义一个新的 mixin 类来修改形状的构造行为,并且您可以具有继承现有三角形和矩形但构造不同的特殊三角形和矩形.

So in this particular case you could define a new mixin class that modifies the construction behaviour of the shapes and you could have specialised triangles and rectangles inheriting from the existing ones but constructed differently.

这篇关于使用类的 __new__ 方法作为工厂:__init__ 被调用两次的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆