Создание наследуемого типа Python с PyCxx - PullRequest
3 голосов
/ 14 февраля 2009

В последнее время мы с другом возились с различными обертками Python C ++, пытаясь найти тот, который отвечает потребностям как профессиональных, так и хобби-проектов. Мы оба оттачивали PyCxx как хороший баланс между легкостью и простотой интерфейса, скрывая при этом некоторые из самых уродливых кусочков API Python C. Однако 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 ]

3 голосов
/ 17 февраля 2009

Вы должны объявить 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 представил классы нового стиля, которые, помимо прочего, позволяют пользователю создавать подклассы встроенных типов (например, вашего нового встроенного типа). Наследование не сработало в вашем примере, потому что оно определяет класс старого стиля.

1 голос
/ 16 февраля 2009

Я только немного поработал с 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 (который, конечно, будет 'dict подкласса, если this экземпляр подкласса) и вызывать getattr_methods только в том случае, если вам не удастся найдите там name (см. функции API PyDict_).

Кроме того, я не думаю, что вы должны устанавливать tp_dealloc самостоятельно: я не вижу, как ваша реализация улучшается по умолчанию PyCxx extension_object_deallocator.

...