Search code examples
pythonc++cythoncythonize

Cython: How can I import multiple class object from a single file?


So this is how I currently structure my files.

CPP Folder
Cython Folder
├── setup.py
└── Geometry
    ├── Circle
    │   ├── __init__.py
    │   ├── Circle.pyx
    │   ├── Circle.pyi
    │   └── Circle.pxd
    └── Point
        ├── __init__.py
        ├── Point.pyx
        ├── Point.pyi
        └── Point.pxd 

And this is my setup.py file

from setuptools import setup, Extension, find_packages
from Cython.Build import cythonize


point_extension = Extension(
    "Geometry.Point.Point",
    [
        "src/Geometry/Point/Point.pyx",
        "../cpp/lib/src/Point.cpp"
    ],
    include_dirs=[
        "../cpp/lib/include"
    ],
    libraries=["Some Library"],
    library_dirs=[
        "src/Geometry"
    ],
    extra_compile_args=["-std=c++17", "-O3"],
    language="c++"
)

circle_extension = Extension(
    "Geometry.Circle.Circle",
    [
        "../cpp/lib/src/Circle.cpp",
        "src/Geometry/Circle/Circle.pyx",
        "../cpp/lib/src/Point.cpp"
    ],
    include_dirs=[
        "../cpp/lib/include"
    ],
    libraries=["Some Library"],
    library_dirs=[
        "src/Geometry"
    ],
    extra_compile_args=["-std=c++17", "-O3"],
    language="c++"
)



setup(
    name="Geometry",
    version="0.1",
    packages=find_packages(where="src"),
    package_dir={"": "src"},
    package_data={
        "Geometry.Circle": ["*.so", "*.pyi"],
        "Geometry.Point": ["*.so", "*.pyi"]
    },
    ext_modules=cythonize([point_extension, circle_extension],
                          compiler_directives={"language_level": 3},
                          include_path=[
                                "../../Expression/cython/src/Some Library",
                                "src/Geometry",
                          ],
                          annotate=True),
    zip_safe=False,
)

With this setup, when I want to import Circle or Point for testing, I have to do as below:

from Geometry.Point import Point
from Geometry.Circle import Circle

And my goal is to be able to import them in this way: from Geometry import Circle, Point

So I think I should structure my file as follows:

CPP Folder
Cython Folder
├── setup.py
└── Geometry
    ├── __init__.py
    ├── Geometry.pyx
    ├── Geometry.pyi
    ├── Geometry.pxd
    ├── Circle
    │   ├── __init__.py
    │   ├── Circle.pyx
    │   ├── Circle.pyi
    │   └── Circle.pxd
    └── Point
        ├── __init__.py
        ├── Point.pyx
        ├── Point.pyi
        └── Point.pxd 

How should I rewrite my setup.py and write my Geometry.pxd, .pyx and .pyi? FYI this is a sample of my Point.pxd and Point.pyx

[Point.pxd]

from libcpp.memory cimport shared_ptr, weak_ptr, make_shared
from Bryan cimport _Bryan, Bryan

cdef extern from "Point.h":
    cdef cppclass _Point:
        _Point(shared_ptr[_Bryan] x, shared_ptr[_Bryan] y, shared_ptr[_Bryan] z)
        shared_ptr[_Bryan] get_x()
        shared_ptr[_Bryan] get_y()
        shared_ptr[_Bryan] get_z()


cdef class Point:
    cdef shared_ptr[_Point] c_point

[Point.pyx]

from Point cimport *

cdef class Point:
    def __cinit__(self, Bryan x=Bryan("0", None), Bryan y=Bryan("0", None), Bryan z=Bryan("0", None)):
        self.c_point = make_shared[_Point](x.thisptr, y.thisptr, z.thisptr)

    def __dealloc(self):
        self.c_point.reset()

    def get_x(self) -> Bryan:
        cdef shared_ptr[_Bryan] result = self.c_point.get().get_x()
        cdef Bryan coord = Bryan("", None, make_with_pointer = True)
        coord.thisptr = result
        return coord

    def get_y(self) -> Bryan:
        cdef shared_ptr[_Bryan] result = self.c_point.get().get_y()
        cdef Bryan coord = Bryan("", None, make_with_pointer = True)
        coord.thisptr = result
        return coord

    def get_z(self) -> Bryan:
        cdef shared_ptr[_Bryan] result = self.c_point.get().get_z()
        cdef Bryan coord = Bryan("", None, make_with_pointer = True)
        coord.thisptr = result
        return coord

    property x:
        def __get__(self):
            return self.get_x()

    property y:
        def __get__(self):
            return self.get_y()

    property z:
        def __get__(self):
            return self.get_z()

Thank you


Solution

  • This is a slightly speculative answer simply because I cannot build an example to compile the Cython modules now. I will assume that your setup.py is successful in compiling the modules.

    You are correct with your proposed directory structure, though you really should use lowercase module names. So:

    CPP Folder
    cython Folder
    ├── setup.py
    └── geometry
        ├── __init__.py
        ├── geometry.pyx
        ├── geometry.pyi
        ├── geometry.pxd
        ├── circle
        │   ├── __init__.py
        │   ├── circle.pyx
        │   ├── circle.pyi
        │   └── circle.pxd
        └── point
            ├── __init__.py
            ├── point.pyx
            ├── point.pyi
            └── point.pxd
    

    You will need to change your setup.py to account for the lowercasing. Once you've done that, you can "hoist" (I don't know if there's a technical term for this) your classes up the directory hierarchy. Then they will be available under the parent (geometry) namespace

    Inside __init__.py in the geometry directory, you put:

    from .circle import Circle
    from .point import Point
    

    The remaining question is whether you even need the other files inside geometry. Once your Circle and Point classes are compiled, I'm not sure what you actually need in the geometry module to be compiled.