Передача аргументов в 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, поэтому для последовательности я настоятельно советую вам не делать этого. Если вам нужна помощь, как добиться этого без пирекс или цитон, дайте мне знать. но я должен предупредить, что требуется некоторое понимание и много кода котельной плиты, чтобы понять