Создание наследуемого типа 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,

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