2

最近、コンストラクターを呼び出さずにオブジェクトをインスタンス化しようとしているときに、C ベースの python 拡張機能で問題に直面しました。これは、拡張機能の要件です。

インスタンスの作成に使用するクラスは動的に取得されます。ある時点で、x他のインスタンスの作成に使用したいクラスを持つインスタンスがあるのでx.__class__、後で使用するために保存します。この値を にしますklass

後で呼び出すPyInstance_NewRaw(klass, PyDict_New())と、問題が発生します。klass古いスタイルのクラスの場合、その呼び出しの結果は目的の新しいインスタンスになるようです。ただし、新しいスタイルのクラスの場合、結果は NULL になり、発生する例外は次のとおりです。

SystemError: ../Objects/classobject.c:521: 内部関数の引数が正しくありません

記録のために、私は Python バージョンを使用してい2.7.5ます。グーグルで調べてみると、解決策を探している人は他に 1 人しかいませんでした (そして、彼は回避策を実行しているように見えましたが、詳細は示していませんでした)。

レコード #2 の場合: 拡張機能が作成しているインスタンスは、これらの同じxインスタンスのプロキシです。x.__class__およびは既知であるため、拡張機能は(前述の C 関数を使用して) にx.__dict__基づいて新しいインスタンスを生成し、それぞれを新しいインスタンスに設定します。インスタンス(これらにはプロセス間共有メモリデータがあります)。インスタンスを 2 回呼び出すことは概念的に問題があるだけでなく(1 つ目: 状態は既にわかっている、2 つ目: ctor の予想される動作は、インスタンスごとに 1 回だけ呼び出す必要がある)、拡張機能を使用できないため、非現実的でもあります。システム内の各インスタンスを呼び出すための引数とその順序を理解してください。また、__class____dict____dict____init____init__()__init__インスタンスがプロキシである可能性のあるシステム内の各クラスの、それらが対象となるプロキシメカニズムがあることを認識させることは、概念的に問題があり (彼らはそれについて知る必要がありません)、実用的ではありません。

だから、私の質問は:PyInstance_NewRawインスタンスのクラス スタイルに関係なく、同じ動作を実行する方法は?

4

1 に答える 1

2

新しいスタイルのクラスの型は ではなくinstance、クラスそのものです。したがって、PyInstance_*メソッドは新しいスタイルのクラスでは意味がありません。

実際、ドキュメントではこれを明示的に説明しています。

ここで説明するクラス オブジェクトは、Python 3 で廃止される古いスタイルのクラスを表すことに注意してください。拡張モジュールの新しい型を作成するときは、型オブジェクトを使用する必要があります (型オブジェクトのセクション)

klassそのため、 が古いスタイルのクラスか新しいスタイルのクラスかをチェックし、それぞれの場合に適切な処理を行うコードを作成する必要があります。古いスタイルのクラスのタイプは ですがPyClass_Type、新しいスタイルのクラスのタイプはPyType_Typeまたはカスタム メタクラスです。

PyInstance_NewRaw一方、 for new-style クラスに直接相当するものはありません。むしろ、tp_allocスロットを呼び出して dict を追加するという直接的な方法では、機能しないクラスが得られます。他の適切な作業をすべて複製することもできますが、それは難しいでしょう。または、 を使用することもできますが、クラス (またはそのベース) にtp_newカスタム関数がある場合、それは間違った動作をします。いくつかのアイデアについては、 #5180__new__の却下されたパッチを参照してください。

しかし、実際には、あなたがやろうとしていることは、そもそも良い考えではないでしょう。なぜこれが必要なのか、何をしようとしているのかを説明すれば、もっと良い方法があるでしょう。


クラスの初期化されていない新しいインスタンスを作成し_dict__、初期化されたプロトタイプからコピーしてオブジェクトを構築することが目標である場合は、はるかに簡単な解決策があり、うまくいくと思います。

__class__書き込み可能な属性です。したがって(Pythonで表示します。C APIは基本的に同じですが、はるかに冗長であり、おそらくどこかでrefcountingを台無しにするでしょう):

class NewStyleDummy(object):
    pass
def make_instance(cls, instance_dict):
    if isinstance(cls, types.ClassType):
        obj = do_old_style_thing(cls)
    else:
        obj = NewStyleDummy()
        obj.__class__ = cls
    obj.__dict__ = instance_dict
    return obj

新しいオブジェクトは のインスタンスになります。cls特に、MRO、メタクラスなどを含む同じクラス ディクショナリを持ちます。

cls構築に必要なメタクラスがある場合、またはカスタムメソッド__new____slots__ある__dict__場合、これは機能しません。何でもうまくいく可能性がある場合は、この単純な解決策がうまくいくと私は信じています。


通話cls.__new__は最初は良い解決策のように思えますが、実際にはそうではありません。その背景を説明しよう。

これを行う場合:

foo = Foo(1, 2)

(Fooは新しいスタイルのクラスです)、次の擬似コードのようなものに変換されます。

foo = Foo.__new__(1, 2)
if isinstance(foo, Foo):
    foo.__init__(1, 2)

問題は、Fooまたはそのベースの 1 つがメソッドを定義している__new__場合、メソッドと同じように、コンストラクター呼び出しから引数を取得することを期待する__init__ことです。

__init__質問で説明したように、コンストラクター呼び出しの引数がわかりません。実際、それがそもそも通常のメソッドを呼び出せない主な理由です。したがって、どちらも呼び出すことはできません__new__

の基本実装は__new__、与えられた引数を受け入れて無視します。そのため、どのクラスにも__new__オーバーライドまたはが含まれていない場合は、癖(ちなみに、Python 3.x では異なる動作を__metaclass__する癖) のために、たまたまこれを回避することになります。object.__new__しかし、これらは以前のソリューションで処理できるのとまったく同じケースであり、そのソリューションははるかに明白な理由で機能します。

別の言い方をすれば、前の解決策は を__new__呼び出さないため、誰も定義しないことに依存しています__new__。この解決策は、間違った引数__new__で呼び出すため、誰も定義しないことに依存しています。__new__

于 2013-09-12T06:50:59.237 に答える