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

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

问题描述

我在python中遇到一个奇怪的错误,其中使用类作为工厂的 __ new __ 方法将导致 __ init __ 实例化类的方法被调用两次。



这个想法最初是使用 __ new __ 母亲课程根据所传递的参数返回其中一名孩子的具体实例,而无需在课外宣布工厂职位。



我知道使用工厂功能将是在这里使用的最佳设计模式,但是在项目的这一点改变设计模式将是昂贵的。我的问题是:有没有办法避免对 __ init __ 的双重调用,并且只得到一个单一的调用 __ init __ 在这样一个模式中?$ /

 类Shape(object):
def __new __(cls,desc):
如果cls是Shape:
如果desc =='big':return Rectangle(desc)
if desc =='small':return Triangle(desc)
else:
返回超级(Shape,cls).__新__(cls,desc)

def __init __(self,desc):
printinit called
self.desc = desc

class三角形(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调用
>>> init调用
>>> 3

非常感谢。

解决方案

当您构建一个对象时,Python调用其 __ new __ 方法来创建对象,然后调用 __ init __ 返回的对象。当您通过调用 Triangle() __ new __ 内创建对象时,将导致进一步调用 __ new __ __ init __



你应该做的是:


 类Shape(object):
def __new __(cls,desc):
如果cls是Shape:
如果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)

which将创建一个 Rectangle Triangle 而不触发调用 __ init __ 然后 __ init __ 只调用一次。



编辑以回答@ Adrian关于超级工作的问题: p>

super(Shape,cls)搜索 cls .__ mro __ 找到 Shape ,然后搜索r发现序列找到属性。



三角形.__ mro __ (三角形,形状,对象)
矩形.__ mro __ (矩形,形状,对象) 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
super(B,cls),如果它是一个B实例,将搜索(A,object)但是如果您有一个 D 实例,则在 B 将搜索(C,A,object),因为 D .__ mro __ (B,C,A,object)



所以在这种特殊情况下,你可以定义一个新的mixin类来修改形状的施工行为,你可以有专门的三角形和直角ngles继承现有的,但构造不同。


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.

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.

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

Any help greatly appreciated.

解决方案

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__.

What you should do is:

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)

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

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

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

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

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).

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天全站免登陆