使用类'__new__方法作为工厂:__ init__被调用两次

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)

这将创建RectangleTriangle不触发调用__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类来修改形状的构造行为,您可以使用从现有构造继承的特殊三角形和矩形,但构造方式不同.

  • 使用`return Rectangle .__ new __(Rectangle)`不是更好吗,因为这样可以保证`Rectate`的`__new__`在被定义时被调用? (2认同)
  • @Georg,如果你这样做,你必须非常小心避免无限递归.任何类特定的初始化都应该在`__init__`中,所以我认为假设`__new__`唯一的工作就是创建一个正确类型的对象,这是非常安全的. (2认同)
  • python 3 中的等价物是什么? (2认同)

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)

  • 为什么你说这不如邓肯的解决方案.对于正在发生的事情来说,这似乎更加清晰,而且不那么黑客.也是元. (2认同)
  • 我认为它不太明显,因为它为程序增加了第四个类,并涉及不是每个人都熟悉的`__metaclass__`黑魔法. (2认同)