Передача аргументов в tp_new и tp_init из подтипов в Python C API

Первоначально я задал этот вопрос в списке capi-sig Python: как передать аргументы в tp_new и tp_init из подтипов?

Я читаю Python PEP-253 по подтипам, и есть много хороших рекомендаций о том, как структурировать типы, звоните tp_new а также tp_init слоты и т. д.

Но ему не хватает важного замечания по передаче аргументов из sub в супертип. Кажется, что PEP-253 является незаконченным согласно примечанию:

(XXX Там должен быть параграф или два о передаче аргумента здесь.)

Итак, я пытаюсь экстраполировать некоторые стратегии, хорошо известные из подтипов классов 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 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)

это было бы похоже на ситуацию Shape а также ColoredShape выше. Я думаю, было бы проще иметь дело с этим.

Может ли кто-нибудь помочь выяснить отсутствующий комментарий XXX, упомянутый выше, и исправить технику для передачи аргументов от подтипа до супертипа (ов) в конструкции?

ОБНОВЛЕНИЕ 2012-07-17:

Вдохновленный ответом ecatmur ниже, я просмотрел источники Python 3 и нашел defdict_init Конструктор Collections.defaultdict Тип объекта интересно. Тип является производным от PyDictObject и его конструктор принимает дополнительный аргумент default_factory, Подпись конструктора в классе Python такова:

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 функция.

3 ответа

Решение

Проблема в том, что PyArgs_ParseTupleAndKeywords не предоставляет способ извлечения дополнительных *args а также **kwargs из входных аргументов и ключевых слов; действительно, любые дополнительные аргументы приводят к TypeError; Msgstr "Функция принимает%s %d позиционных аргументов (задано% d)", или "%U" является недопустимым аргументом ключевого слова для этой функции ".

Это означает, что вам придется самостоятельно анализировать аргументы и ключевые слова; вам гарантировано, что args - это кортеж, а ключевые слова - это диктат, поэтому вы можете использовать стандартные методы (PyTuple_GET_ITEM а также PyDict_GetItemString) для извлечения аргументов, которые вас интересуют, а также для определения и построения кортежа и передачи из остатка. Вы, очевидно, не можете изменять аргументы, потому что кортежи неизменны; и хотя извлечение элементов из ключевых слов должно быть в порядке, это кажется немного рискованным ( пример сбоя).

Более амбициозный, но, безусловно, выполнимый путь будет копировать vgetargskeywords от getargs.c ( http://hg.python.org/cpython/file/tip/Python/getargs.c) и расширьте его, чтобы принять необязательные выходные параметры для остатка *args а также **kwargs, Это должно быть довольно просто, так как вам просто нужно изменить части, где он обнаруживает и бросает TypeError на дополнительные аргументы ( дополнительные аргументы; дополнительные ключевые слова). Удачи, если вы выберете этот маршрут.

Хорошо, чтобы ответить на ваш первый вопрос: во-первых, у вас есть структура C, представляющая ваш объект класса, но со стороны C. Так что в заголовочном файле с именем Shapes.h

typedef struct {
PyObject_HEAD
char *shapename;
} Shape;

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

Здесь следует отметить следующее:

  • shapename и color по сути являются частными переменными. 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 C, импортированным из Python. В ответ на ваш второй вопрос, ваша функция инициализации будет выглядеть следующим образом:

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, чтобы лучше понять его

Я углубился в python c api довольно долго, и это, по меньшей мере, странный зверь. Возможно, стоит заглянуть в пирекса или цифона, которые позаботятся о многих безобразиях для вас. С точки зрения Python нет причин, по которым вы можете изменить аргументы. однако вы не можете изменить порядок членов в C, поэтому для последовательности я настоятельно советую вам не делать этого. Если вам нужна помощь, как добиться этого без пирекс или цитон, дайте мне знать. но я должен предупредить, что требуется некоторое понимание и много кода котельной плиты, чтобы понять

Другие вопросы по тегам