9

我最初在 Python capi-sig 列表中问了这个问题:How to pass arguments to tp_new and tp_init from subtypes?

我正在阅读关于子类型化的 Python PEP-253,关于如何构造类型、调用tp_newtp_init插槽等有很多很好的建议。

但是,它缺少关于将参数从 sub 类型传递到 super 类型的重要说明。根据注释, PEP-253似乎未完成:

(XXX 这里应该有一两段关于参数传递的内容。)

因此,我试图从 Python 类 subtyping 中推断出一些众所周知的策略,尤其是每个级别剥离参数的技术等。

我正在寻找实现与此类似效果的技术,但使用普通的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 中的等价物是什么?

如何处理类似的情况,但特定于派生类的参数以不同的顺序预期?kwds它是在 args 元组(或dict,我认为原理相同)末尾给出的参数。

下面是一些(伪)代码来说明这种情况:

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)

这将ShapeColoredShape上述情况类似。我想这也会更容易处理。

任何人都可以帮助找出上面提到的缺失的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没有提供从输入参数和关键字*args中提取额外内容的方法;**kwargs实际上,任何额外的参数都会导致TypeError; “函数采用 %s %d 个位置参数(已给出 %d 个)”,或“'%U' 是此函数的无效关键字参数”。

这意味着您将不得不自己解析参数和关键字;您可以保证 args 是一个元组而关键字是一个 dict,因此您可以使用标准方法 (PyTuple_GET_ITEMPyDict_GetItemString) 来提取您感兴趣的参数,并识别和构造一个元组和 dict 以从其余部分传递。您显然不能修改 args,因为元组是不可变的;虽然从关键字中弹出项目应该没问题,但它看起来确实有点冒险(例如崩溃)。

一个更雄心勃勃但绝对可行的路线是vgetargskeywordsgetargs.chttp://hg.python.org/cpython/file/tip/Python/getargs.c)复制并扩展它以采用可选的 out-parameters*args**kwargs. 这应该相当简单,因为您只需要修改它检测到的部分并抛出TypeError额外的参数(额外的参数额外的关键字)。如果你选择这条路线,祝你好运。

于 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 的角度定义了对象,即它的方法、成员和它具有的任何特殊方法。这也是我们告诉 Python ColouredShape 是 Shape 的子类的地方。它如下所示:

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 */
};

需要注意的重要一点是mymod必须是从 Python 导入的 Python C 扩展名。在回答您的第二个问题时,您的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

我已经深入研究了python c api,至少可以说它是一个奇怪的野兽。可能值得研究 pyrex 或 cython,它们可以为您解决很多丑陋的问题。从 python 的角度来看,没有理由可以更改参数。但是您不能更改 C 中成员的顺序,因此为了保持一致性,我强烈建议您不要这样做。如果您需要有关如何在没有 pyrex 或 cython 的情况下实现此目标的帮助,请告诉我。但我应该警告它需要一些理解和大量样板代码才能理解

于 2012-07-12T10:14:59.043 回答