问题描述
我在python中遇到了一个奇怪的错误,其中使用类的__new__作为工厂的__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
任何帮助都非常感谢.
推荐答案
构造对象时,python调用其__new__方法来创建对象,然后在返回的对象上调用__init__.当您通过调用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),但是如果您有A D实例,则B中的相同调用将搜索(C, A, object),因为
因此,在这种特殊情况下,您可以定义一个新的Mixin类,该类别修改形状的构建行为,并且您可以从现有的三角形和矩形中使用专门的三角形和矩形,但构建不同.
.其他推荐答案
发布了我的问题后,我继续搜索解决方案,找到了一种方法来解决看起来像黑客的问题.它不如邓肯的解决方案,但我认为提到仍然很有趣. 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
其他推荐答案
我实际上无法在我安装的任何Python口译员中重现此行为,因此这是一个猜测.但是...
__init__被称为两次,因为您正在初始化两个对象:原始Shape对象,然后是其一个子范围之一.如果您更改__init__,所以它还打印了要初始化的对象的类,您将看到此.
print type(self), "init called"
这是无害的,因为原始Shape将被丢弃,因为您没有在__new__()中返回对其的引用.
由于调用函数是在语法上相同的以实例化类,因此您可以将其更改为函数而无需更改其他任何内容,我建议您确实做到这一点.我不明白你的不情愿.
问题描述
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.
其他推荐答案
After posting my question, I continued searching for a solution an found a way to solve the problem that looks like a bit of a hack. It is inferior to Duncan's solution, but I thought it could be interesting to mention none the less. The Shapeclass becomes:
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
其他推荐答案
I can't actually reproduce this behavior in either of the Python interpreters I have installed, so this is something of a guess. However...
__init__ is being called twice because you are initializing two objects: the original Shape object, and then one of its subclasess. If you change your __init__ so it also prints the class of the object being initialized, you will see this.
print type(self), "init called"
This is harmless because the original Shape will be discarded, since you are not returning a reference to it in your __new__().
Since calling a function is syntactically identical to instantiating a class, you can change this to a function without changing anything else, and I recommend that you do exactly that. I don't understand your reluctance.