首页 > 解决方案 > 使用 Cython 访问包装对象的成员变量

问题描述

我正在使用我以前从未使用过的 Cython 将现有的 C/C++ 库连接到 Python。它进展顺利,除了以下场景,我使用模型示例进行描述:

假设我有一个类“Person”,它有一个成员变量,一个名为“id”的int,另一个类“Group”有一个成员变量,一个名为“leader”的Person。类 Person 和 Group 还具有用于获取和设置其各自成员变量的成员函数,如下面的代码所示。我想要做的是:给定一个有领导的组,我希望能够访问组领导的 id(读取和修改)。例如,我希望下面的 Python 代码

from pyperson import PyPerson
from pygroup import PyGroup

nancy = PyPerson(7) # Create new person with id=7
my_group = PyGroup(nancy) # Create new group with leader=nancy
my.group.get_leader().set_id(12)
print(nancy.get_id())

输出数字 12。

我知道这个问题之前已经在这个网站上描述过(参见帖子底部的链接),但由于缺乏 C++ 能力,我无法成功地将那里给出的答案转移到我的虚拟示例中。

我写的代码如下:

人.h

#ifndef PERSON_H
#define PERSON_H

class Person {
    private:
        int my_id;
    public:
        Person();
        Person(int id);
        int getId();
        void setId(int id);
};
#endif

个人.cpp

#include "person.h"

// Default constructor
Person::Person() {}

// Overloaded constructor
Person::Person(int id) {
    this->setId(id);
}

// Get and set id
int Person::getId() {
    return this->my_id;
}
void Person::setId(int id) {
    this->my_id = id;
}

cperson.pxd

cdef extern from "person.cpp":
    pass

cdef extern from "person.h":
    cdef cppclass Person:
        Person() except +
        Person(int) except +
        int getId();
        void setId(int);

pyperson.pyx

# distutils: language = c++

from cperson cimport Person

cdef class PyPerson:
    cdef Person c_person

    def __cinit__(self, int id):
        self.c_person = Person(id)

    def get_id(self):
        return self.c_person.getId()
        
    def set_id(self, int id):
        self.c_person.setId(id)

组.h

#ifndef GROUP_H
#define GROUP_H
#include "person.h"

class Group {
    private:
        Person* my_leader;
    public:
        Group();
        Group(Person& leader);
        Person* getLeader();
        void setLeader(Person& leader);
};
#endif

组.cpp

#include "group.h"
#include "person.h"

// Default constructor
Group::Group() {}

// Overloaded constructor
Group::Group(Person& leader) {
    this->setLeader(leader);
}

// Get and set leader
Person* Group::getLeader() {
    return this->my_leader;
}
void Group::setLeader(Person& leader) {
    this->my_leader = &leader;
}

cgroup.pxd

from cperson cimport Person

cdef extern from "group.cpp":
    pass

cdef extern from "group.h":
    cdef cppclass Group:
        Group() except +
        Group(Person&) except +
        Person* getLeader();
        void setLeader(Person&);

pygroup.pyx

# distutils: language = c++

from cperson cimport Person
from cgroup cimport Group
from pyperson import PyPerson

cdef class PyGroup:
    cdef Group c_group

    def __cinit__(self, Person& leader):
        self.c_group = Group(leader)

    def get_leader(self):
        cdef PyPerson leader = PyPerson(0)
        leader.c_person = self.c_group.getLeader()
        return leader
        
    def set_leader(self, Person& leader):
        self.c_group.setLeader(leader)

安装程序.py

from setuptools import setup
from Cython.Build import cythonize
setup(ext_modules=cythonize(["pyperson.pyx", "pygroup.pyx"]))

在包含上述代码的文件夹中执行命令“python3 setup.py build_ext --inplace”会产生以下错误消息:

Error compiling Cython file:
------------------------------------------------------------
...

    def __cinit__(self, Person& leader):
        self.c_group = Group(leader)

    def get_leader(self):
        cdef PyPerson leader = PyPerson(0)
            ^
------------------------------------------------------------

pygroup.pyx:14:13: 'PyPerson' is not a type identifier

Error compiling Cython file:
------------------------------------------------------------
...
    def __cinit__(self, Person& leader):
        self.c_group = Group(leader)

    def get_leader(self):
        cdef PyPerson leader = PyPerson(0)
        leader.c_person = self.c_group.getLeader()
                                               ^
------------------------------------------------------------

pygroup.pyx:15:48: Cannot convert 'Person *' to Python object
Traceback (most recent call last):
  File "setup.py", line 5, in <module>
    setup(ext_modules=cythonize(["pyperson.pyx", "pygroup.pyx"]))
  File "/home/sindre/.local/lib/python3.8/site-packages/Cython/Build/Dependencies.py", line 1102, in cythonize
    cythonize_one(*args)
  File "/home/sindre/.local/lib/python3.8/site-packages/Cython/Build/Dependencies.py", line 1225, in cythonize_one
    raise CompileError(None, pyx_file)
Cython.Compiler.Errors.CompileError: pygroup.pyx

在解决这些错误和实现既定目标方面的任何帮助将不胜感激。

正如所承诺的,这里是我检查过的 stackoverflow-posts 的链接: 如何从另一个包装的对象返回 Cython 中的包装 C++ 对象? 如何在 Cython 中返回 C++ 包装对象 Cython - 将 C++ 函数返回的 C++(向量和非向量)对象 暴露给 Python 如何在不复制对象的情况下将返回 C++ 对象的函数暴露给 Python?

编辑:

根据 DavidW 的建议,我将 pygroup.pyx 中的第 5 行更改为阅读

from pyperson cimport PyPerson

但这并没有解决我的问题。相反,我读到了一个额外的编译错误

pygroup.pyx:5:0: 'pyperson.pxd' not found

考虑到 PyPerson 是在 pyperson.pyx 中定义的,而不是在 pyperson.pxd 中,我认为这个错误是有道理的。

我进行了更多实验,看看我是否可以自己解决错误,并最终在 pygroup.pyx 中得到以下代码(我没有更改上述任何其他文件):

# distutils: language = c++

from cperson cimport Person
from cgroup cimport Group
from pyperson import PyPerson

cdef class PyGroup:
    cdef Group c_group

    def __cinit__(self, Person& leader):
        self.c_group = Group(leader)

    def get_leader(self):
        leader = PyPerson(0)
        leader.c_person = self.c_group.getLeader()
        return leader
        
    def set_leader(self, Person& leader):
        self.c_group.setLeader(leader)

使用上面的代码,我只收到以下错误:

Error compiling Cython file:
------------------------------------------------------------
...
    def __cinit__(self, Person& leader):
        self.c_group = Group(leader)

    def get_leader(self):
        leader = PyPerson(0)
        leader.c_person = self.c_group.getLeader()
                                               ^
------------------------------------------------------------

pygroup.pyx:15:48: Cannot convert 'Person *' to Python object

这让我很困惑,因为leader.c_person考虑到它c_person是使用cdef. 我也不明白如何将 Person 的实例转换为 Python 可访问的东西。

标签: pythonc++objectcython

解决方案


推荐阅读