xAp*_*ple 36 python inheritance design-patterns class-design
我在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
Run Code Online (Sandbox Code Playgroud)
任何帮助非常感谢.
Dun*_*can 56
构造对象时,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)
Run Code Online (Sandbox Code Playgroud)
这将创建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
Run Code Online (Sandbox Code Playgroud)
现在B中的一个方法可能会使用super(B, cls),如果它是一个B实例会搜索,(A, object)但如果你有一个D实例,同一个调用B将搜索,(C, A, object)因为它D.__mro__是(B, C, A, object).
因此,在这种特殊情况下,您可以定义一个新的mixin类来修改形状的构造行为,您可以使用从现有构造继承的特殊三角形和矩形,但构造方式不同.
xAp*_*ple 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
Run Code Online (Sandbox Code Playgroud)