我在python中遇到一个奇怪的错误,其中使用类的__new__
方法作为工厂会导致实例化类的__init__
方法被调用两次。
这个想法最初是使用母类的__new__
方法根据传递的参数返回她的一个孩子的特定实例,而不必在类之外声明工厂函数。
我知道使用工厂功能将是这里使用的最佳设计模式,但是在项目的这一点上改变设计模式将是昂贵的。我的问题是:有没有办法避免对__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
非常感谢任何帮助。
答案 0 :(得分:53)
构造对象时,Python调用其__new__
方法来创建对象,然后在返回的对象上调用__init__
。当您通过调用__new__
从Triangle()
内部创建对象时,会导致进一步调用__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(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, object)
。
因此,在这种特殊情况下,您可以定义一个新的mixin类来修改形状的构造行为,您可以使用专门的三角形和矩形继承现有的但不同的构造。
答案 1 :(得分:11)
在发布我的问题后,我继续寻找解决方案,找到一种方法来解决看起来有点像黑客的问题。它不如Duncan的解决方案,但我认为提到这一点可能会很有趣。 Shape
类成为:
class ShapeFactory(type):
def __call__(cls, desc):
if cls is Shape:
if desc == 'big': return Rectangle(desc)
if desc == 'small': return Triangle(desc)
return type.__call__(cls, desc)
class Shape(object):
__metaclass__ = ShapeFactory
def __init__(self, desc):
print "init called"
self.desc = desc
答案 2 :(得分:0)
我无法在我安装的任何一个Python解释器中重现这种行为,所以这是一种猜测。然而...
__init__
被调用两次,因为您正在初始化两个对象:原始Shape
对象,然后是其中一个子类。如果您更改了__init__
,那么它也会打印正在初始化的对象的类,您将看到这一点。
print type(self), "init called"
这是无害的,因为原始Shape
将被丢弃,因为您没有在__new__()
中返回对它的引用。
由于调用一个函数在语法上相同来实例化一个类,你可以将它更改为一个函数而不改变其他任何东西,我建议你这样做。我不明白你的不情愿。