Accessing members variables of wrapped objects using Cython

  c++, cython, object, python

I am working on interfacing an existing C/C++ library to Python using Cython, which I have never used before. It’s been going pretty well, except for the following scenario, which I describe using a mock-up example:

Suppose I have a class ‘Person’ with one member variable, an int named ‘id’, and another class ‘Group’ with one member variable, a Person named ‘leader’. The classes Person and Group also have member functions for getting and setting their respective member variables, as shown in the code below. What I want to do is this: Given a group with a leader, I want to be able to access the id of the group’s leader (both read and modify). For example, I would expect the following Python code

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())

to output the number 12.

I am aware that this issue has been described on this site before (see links at the bottom of the post), but due to a lack of C++ competence, I have been unable to successfully transfer the answers given there to my dummy example.

The code I have written is as follows:

person.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

person.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)

group.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

group.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)

setup.py

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

Executing the command "python3 setup.py build_ext –inplace" in a folder containing the code above yields the following error message:

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

Any help in resolving these errors and achieving the stated goal would be greatly appreciated.

As promised, here are the links to the stackoverflow-posts I have checked out:
How do I return wrapped C++ Objects in Cython from another wrapped Object?
How to return a C++ wrapped object in Cython
Cython – Exposing C++ (vector and non-vector) objects returned by C++ function to Python
How to expose a function returning a C++ object to Python without copying the object?

Source: Windows Questions C++

LEAVE A COMMENT