pythonで、クラスの__new__
メソッドをファクトリとして使用すると、インスタンス化されたクラスの__init__
メソッドが2回呼び出されるという奇妙なバグが発生しました。
当初のアイデアは、母クラスの__new__
メソッドを使用して、クラスの外部でファクトリ関数を宣言することなく、渡されたパラメータに応じて子の1つの特定のインスタンスを返すことでした。
ここで使用するのにファクトリ関数を使用するのが最適なデザインパターンであることはわかっていますが、プロジェクトのこの時点でデザインパターンを変更するとコストがかかります。したがって、私の質問は、__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__
を呼び出します。__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)
これにより、__init__
の呼び出しをトリガーせずに、Rectangle
またはTriangle
が作成され、__init__
が1回だけ呼び出されます。
スーパーがどのように機能するかについての@Adrianの質問に答えるために編集してください:
super(Shape,cls)
はcls.__mro__
を検索してShape
を見つけ、次にシーケンスの残りを検索して属性を見つけます。
Triangle.__mro__
は(Triangle, Shape, object)
であり、Rectangle.__mro__
は(Rectangle, Shape, object)
であり、Shape.__mro__
は単なる(Shape, object)
です。 super(Shape, cls)
を呼び出すと、Shape
までのmroシーケンス内のすべてが無視されるため、残っているのは単一要素のタプル(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)
です。
したがって、この特定のケースでは、シェイプの構築動作を変更する新しいミックスインクラスを定義し、既存のものを継承しているが異なる方法で構築された特殊な三角形と長方形を作成できます。
質問を投稿した後、私は解決策を探し続け、ちょっとしたハックのように見える問題を解決する方法を見つけました。ダンカンのソリューションより劣っていますが、それでも言及するのは面白いかもしれないと思いました。 Shape
classは次のようになります。
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
オブジェクトと、そのサブクラスの1つという2つのオブジェクトを初期化しているため、2回呼び出されています。 ___init__
_を変更して、初期化されているオブジェクトのクラスも出力するようにすると、これが表示されます。
_print type(self), "init called"
_
元のShape
は、__new__()
で参照を返さないため、破棄されるため、これは無害です。
関数の呼び出しは、クラスのインスタンス化と構文的に同一であるため、他に何も変更せずにこれを関数に変更できます。正確に行うことをお勧めします。私はあなたの不本意を理解していません。