9

私はもともと Python の capi-sig リストでこの質問をしました:サブタイプから tp_new と tp_init に引数を渡すには?

サブタイピングに関する Python PEP-253を読んでいますが、型、呼び出しtp_newtp_initスロットなどを構造化する方法について、適切な推奨事項がたくさんあります。

ただし、サブタイプからスーパータイプへの引数の受け渡しに関する重要な注意事項が欠けています。注記によると、PEP-253は未完成のようです。

(XXX ここで引数の受け渡しについて 1 つか 2 つのパラグラフが必要です。)

そのため、Python クラスのサブタイプからよく知られているいくつかの戦略、特に各レベルで引数を取り除く手法などを推定しようとしています。

これと同様の効果を達成するための手法を探していますが、プレーンなPython C API (3.x)を使用しています。

class Shape:
    def __init__(self, shapename, **kwds):
        self.shapename = shapename
        super().__init__(**kwds)

class ColoredShape(Shape):
    def __init__(self, color, **kwds):
        self.color = color
        super().__init__(**kwds)

Python C API で同等のものは何ですか?

同様の状況に対処する方法はありますが、異なる順序で期待される派生クラスに固有の引数がありますか? これは args タプルの最後に与えられた引数です (またはkwds辞書、原則は同じだと思います)。

状況を示す (疑似) コードを次に示します。

class Base:
   def __init__(self, x, y, z):
      self.x = x
      self.y = y
      self.z = z

class Derived(Base):
   def __init__(self, x, y, a):
      self.a = a
      super().__init__(x, y, None):

aが最初に期待されていた場合は、次の点に注意してください。

Derived.__init__(self, a, x, y)

上記と同様の状況にShapeなりColoredShapeます。また、対処も簡単になると思います。

上記の欠落しているXXXコメントと、構築時にサブタイプからスーパータイプに引数を渡すための正しいテクニックを理解するのを手伝ってくれる人はいますか?

更新 2012 年 7 月 17 日:

以下のecatmurの回答に触発されて、Python 3のソースを調べたところ、 collections.defaultdictタイプのオブジェクトdefdict_initのコンストラクターが 興味深いことがわかりました。型は から派生し、そのコンストラクターは の追加引数を取ります。Python クラスのコンストラクタ シグネチャは次のとおりです。PyDictObjectdefault_factory

class collections.defaultdict([default_factory[, ...]])

ここで、default_factoryが元のタプルからどのように削除されるかを次に示しますargs。したがって、残りの引数はtp_init基本型の に転送されPyDictObjectます。

int result;
PyObject *newargs;
Py_ssize_t n = PyTuple_GET_SIZE(args);
...
newargs = PySequence_GetSlice(args, 1, n);
...
result = PyDict_Type.tp_init(self, newargs, kwds);

これは、関数の関連部分のみの存在を切り取っていることに注意してくださいdefdict_init

4

3 に答える 3

7

問題は、入力引数とキーワードから余分PyArgs_ParseTupleAndKeywordsなを抽出する方法が提供されていないことです。実際、余分な引数は;になります。「関数は %s %d 位置引数を取ります (%d 指定)」、または「'%U' はこの関数の無効なキーワード引数です」。*args**kwargsTypeError

これは、引数とキーワードを自分で解析する必要があることを意味します。args がタプルで、keywords が dict であることが保証されているため、標準メソッド (PyTuple_GET_ITEMおよびPyDict_GetItemString) を使用して、関心のある引数を抽出し、タプルと dict を特定して構築し、残りから渡すことができます。タプルは不変であるため、明らかに引数を変更することはできません。キーワードからアイテムをポップすることは問題ないはずですが、少し危険に思えます (クラッシュの例)。

より野心的ですが確実に実行可能なルートは、 ( http://hg.python.org/cpython/file/tip/Python/getargs.cvgetargskeywords )からコピーし、それを拡張して、残りのオプションの out-parameters と を取得することです。これは、余分な引数 ( extra args ; extra キーワード)を検出してスローする部分を変更するだけなので、かなり簡単です。このルートを選択した場合は、幸運を祈ります。getargs.c*args**kwargsTypeError

于 2012-07-12T13:32:56.147 に答える
2

最初の質問に答えます。まず、クラス オブジェクトを表す C 構造体がありますが、C 側からのものです。つまり、Shapes.h というヘッダー ファイルで

typedef struct {
PyObject_HEAD
char *shapename;
} Shape;

typedef struct {
PyObject_HEAD
    char *shapename;
char *color;
} ColouredShape;

ここで注意すべき点は次のとおりです。

  • shapenamecolorは実質的にプライベート変数です。Python はそれらを認識したり操作したりできません
  • ColouredShape は、継承が機能し、同じ順序で表示されるように、Shape のすべてのパラメーターを定義する必要があります。

次に、クラスの型が必要です。型は基本的に Python の観点からオブジェクトを定義します。つまり、オブジェクトのメソッド、メンバー、およびオブジェクトが持つ特別なメソッドです。ColouredShape が Shape のサブクラスであることを Python に伝える場所でもあります。次のようになります。

PyTypeObject ShapeType = {
PyObject_HEAD_INIT(NULL)
0, /*ob_size*/
"mymod.Shape", /*tp_name*/
sizeof(ShapeType), /*tp_basicsize*/
0, /*tp_itemsize*/
(destructor) SimpleParameter_dealloc, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash */
0, /*tp_call*/
Shape__str__, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
"A class representing a Shape", /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
Shape_methods, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc) Shape_init, /* tp_init */
0, /* tp_alloc */
Shape_new, /* tp_new */
};

PyTypeObject ColouredShapeType = {
PyObject_HEAD_INIT(NULL)
0, /*ob_size*/
"mymod.ColouredShape", /*tp_name*/
sizeof(ColouredShape), /*tp_basicsize*/
0, /*tp_itemsize*/
(destructor) ColouredShape_dealloc, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash */
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
"A class representing a coloured shape", /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
ColouredShape_methods, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
&ShapeType, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc) ColouredShape_init, /* tp_init */
0, /* tp_alloc */
ColouredShape_new, /* tp_new */
};

注意すべき重要な点の 1 つは、mymodは、Python からインポートされた Python C 拡張機能の名前でなければならないということです。2 番目の質問への回答として、init関数は次のようになります。

int Shape_init(Shape *self, PyObject *args, PyObject *kwds){
   char *colour = null;
   static char *kwdlist[] = {"colour", NULL};
   if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwdlist,
&colour)){
return -1;
}
   //Initialise your object here
}

ColouredShape_init が Shape_init を呼び出せない理由はありません。ただし、 PyArgs_ParseTupleAndKeywords の私の理解は次のとおりです。

  • 残りの位置引数とキーワード引数のフォーマット パラメータはありません
  • 引数とキーワードは変更されません

あなたがその道に行こうとした場合、あなたの困難はどこにあるでしょう。

他にご不明な点がございましたら、お気軽にお問い合わせください。しかし、それをよりよく理解するために PyArgs_ParseTupleAndKeywords を見ることをお勧めします

于 2012-07-12T11:32:10.553 に答える
0

控えめに言っても、私はpythoncapiとその奇妙な獣をある程度掘り下げました。醜いものの多くを処理するパイレックスやcythonを調べる価値があるかもしれません。Pythonの観点からは、引数を変更できる理由はありません。ただし、Cではメンバーの順序を変更できないため、一貫性を保つために変更しないことを強くお勧めします。パイレックスやcythonを使用せずにこれを実現する方法についてサポートが必要な場合は、お知らせください。しかし、理解するにはある程度の理解と多くのボイラープレートコードが必要であることを警告する必要があります

于 2012-07-12T10:14:59.043 に答える