Создание наследуемого типа Python с PyCxx
В последнее время мы с другом возились с различными обертками Python C++, пытаясь найти тот, который отвечает потребностям как профессиональных, так и хобби-проектов. Мы оба оттачивали PyCxx как хороший баланс между легкостью и простотой интерфейса, скрывая при этом некоторые из самых уродливых битов Python C API. Однако PyCxx не очень устойчив, когда дело доходит до представления типов (т. Е. Он инструктирует вас создавать фабрики типов, а не реализовывать конструкторы), и мы работаем над заполнением пробелов, чтобы представить наши типы более функциональным образом., Чтобы заполнить эти пробелы, обратимся к C api.
Это оставило нас с некоторыми вопросами, однако, документация API, кажется, не охватывает слишком глубоко (и когда это происходит, ответы иногда противоречивы). Основной всеобъемлющий вопрос заключается просто в следующем: что нужно определить, чтобы тип Python функционировал как базовый тип? Мы обнаружили, что для того, чтобы класс PyCxx функционировал как тип, нам нужно явно определить tp_new и tp_dealloc и установить тип как атрибут модуля, и что нам нужно установить Py_TPFLAGS_BASETYPE в [наш тип]->tp_flags, но за его пределами что мы все еще нащупываем в темноте.
Вот наш код на данный момент:
class kitty : public Py::PythonExtension<kitty> {
public:
kitty() : Py::PythonExtension<kitty>() {}
virtual ~kitty() {}
static void init_type() {
behaviors().name("kitty");
add_varargs_method("speak", &kitty::speak);
}
static PyObject* tp_new(PyTypeObject *subtype, PyObject *args, PyObject *kwds) {
return static_cast<PyObject*>(new kitty());
}
static void tp_dealloc(PyObject *obj) {
kitty* k = static_cast<kitty*>(obj);
delete k;
}
private:
Py::Object speak(const Py::Tuple &args) {
cout << "Meow!" << endl;
return Py::None();
}
};
// cat Module
class cat_module : public Py::ExtensionModule<cat_module> {
public:
cat_module() : Py::ExtensionModule<cat_module>("cat") {
kitty::init_type();
// Set up additional properties on the kitty type object
PyTypeObject* kittyType = kitty::type_object();
kittyType->tp_new = &kitty::tp_new;
kittyType->tp_dealloc = &kitty::tp_dealloc;
kittyType->tp_flags |= Py_TPFLAGS_BASETYPE;
// Expose the kitty type through the module
module().setAttr("kitty", Py::Object((PyObject*)kittyType));
initialize();
}
virtual ~cat_module() {}
};
extern "C" void initcat() {
static cat_module* cat = new cat_module();
}
И наш тестовый код Python выглядит так:
import cat
class meanKitty(cat.kitty):
def scratch(self):
print "hiss! *scratch*"
myKitty = cat.kitty()
myKitty.speak()
meanKitty = meanKitty()
meanKitty.speak()
meanKitty.scratch()
Любопытно, что если вы закомментируете все биты meanKitty, сценарий запустится, и кошка будет просто мяукать, но если вы раскомментируете класс meanKitty, вдруг Python даст нам следующее:
AttributeError: 'kitty' object has no attribute 'speak'
Что смущает меня. Как будто наследование от него полностью скрывает базовый класс! Если бы кто-нибудь мог дать некоторое представление о том, чего нам не хватает, это будет оценено! Спасибо!
РЕДАКТИРОВАТЬ: Хорошо, примерно через пять секунд после публикации я вспомнил кое-что, что мы хотели попробовать ранее. Я добавил следующий код для котенка -
virtual Py::Object getattr( const char *name ) {
return getattr_methods( name );
}
И теперь мы мяукаем на обоих котят в Python! все еще не полностью там, однако, потому что теперь я получаю это:
Traceback (most recent call last):
File "d:\Development\Junk Projects\PythonCxx\Toji.py", line 12, in <module>
meanKitty.scratch()
AttributeError: scratch
Так что все еще ищу некоторую помощь! Спасибо!
2 ответа
Вы должны объявить kitty
как class new_style_class: public Py::PythonClass< new_style_class >
, Увидеть simple.cxx
и тестовый пример Python по адресу http://cxx.svn.sourceforge.net/viewvc/cxx/trunk/CXX/Demo/Python3/.
В Python 2.2 представлены классы нового стиля, которые, помимо прочего, позволяют пользователю создавать подклассы встроенных типов (например, вашего нового встроенного типа). Наследование не сработало в вашем примере, потому что оно определяет класс старого стиля.
Я только немного поработал с PyCxx, и я не работаю с компилятором, но я подозреваю, что то, что вы видите, похоже на следующую ситуацию, выраженную в чистом Python:
>>> class C(object):
... def __getattribute__(self, key):
... print 'C', key
...
>>> class D(C):
... def __init__(self):
... self.foo = 1
...
>>> D().foo
C foo
>>>
Я думаю, что C++ getattr
метод должен проверить this.ob_type->tp_dict
(который, конечно, будет диктантом подкласса, если this
экземпляр подкласса) и только вызов getattr_methods
если вы не можете найти name
там (см. функции API PyDict_).
Кроме того, я не думаю, что вы должны установить tp_dealloc
себя: я не вижу, как ваша реализация улучшается по умолчанию PyCxx extension_object_deallocator
,