使用PyCxx创建可继承的Python类型

时间:2009-02-14 03:44:09

标签: python python-c-api 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'

这让我感到困惑。就像从它继承一样完全隐藏了基类!如果有人能够提供一些我们缺少的东西的洞察力,将不胜感激!谢谢!


编辑:好的,所以大约五秒钟发布后我回忆起我们之前想要尝试的东西。我将以下代码添加到kitty -

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 个答案:

答案 0 :(得分:3)

您必须将kitty声明为class new_style_class: public Py::PythonClass< new_style_class >。请参阅simple.cxx上的{{1}}和Python测试用例。

Python 2.2引入了新式类,其中包括允许用户子类化内置类型(如新的内置类型)。继承在您的示例中不起作用,因为它定义了旧式类。

答案 1 :(得分:1)

我只使用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是子类的一个实例)并且只调用{ {1}}如果你在那里找不到getattr_methods(参见PyDict_ API函数)。

另外,我认为你不应该自己设置name:我没有看到你的实现如何改进PyCxx的默认tp_dealloc