我在 python 中遇到了一个奇怪的错误,将类的 __new__
方法用作工厂会导致实例化类的 __init__
方法被调用两次。
这个想法最初是使用母类的 __new__
方法根据传递的参数返回她的一个 child 的特定实例,而不必在外部声明工厂函数类(class)。
我知道使用工厂函数将是在这里使用的最佳设计模式,但在项目的这个阶段更改设计模式的成本会很高。因此,我的问题是:有没有办法避免对 __init__
的双重调用而在这种模式中只对 __init__
进行一次调用?
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__
.
你应该做的是:
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)
这将创建一个 Rectangle
或 Triangle
而不会触发对 __init__
的调用,然后仅调用 __init__
一次。
编辑以回答@Adrian 关于 super 如何工作的问题:
super(Shape,cls)
搜索 cls.__mro__
以找到 Shape
,然后向下搜索序列的其余部分以找到属性.
Triangle.__mro__
是 (Triangle, Shape, object)
和
Rectangle.__mro__
是 (Rectangle, Shape, object)
而 Shape.__mro__
只是 (Shape, object)
.
对于任何这些情况,当您调用 super(Shape, cls)
时,它会忽略 mro 序列中的所有内容,包括 Shape
所以唯一剩下的就是单个元素元组(object,)
用于查找所需的属性。
如果您有菱形继承(钻石问题),这将变得更加复杂:
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, 对象)
.
因此,在这种特殊情况下,您可以定义一个新的 mixin 类来修改形状的构造行为,并且您可以有专门的三角形和矩形继承自现有的,但构造不同。
关于python - 使用类的 __new__ 方法作为工厂 : __init__ gets called twice,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5953759/