You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Cython配置问题:从单个pyx文件导入多类时出现循环导入错误

问题描述

我有一个名为Geometry.pyx的文件,里面定义了Segment、Angle和Circle三个类,代码如下:

cimport Geometry

# Define Python classes
cdef class Segment:
    def __cinit__(self, float length):
        self.c_segment = make_shared[_Segment](length)

    def get_length(self) -> float:
        return self.c_segment.get().get_length()

cdef class Angle:

    def __cinit__(self, float degrees):
        self.c_angle = make_shared[_Angle](degrees)

    def get_degrees(self) -> float:
        return self.c_angle.get().get_degrees()

    def get_radians(self) -> float:
        return self.c_angle.get().get_radians()

cdef class Circle:

    def __cinit__(self, float radius):
        self.c_circle = make_shared[_Circle](radius)

    def get_radius(self) -> float:
        return self.c_circle.get().get_radius()

    def get_diameter(self) -> float:
        return self.c_circle.get().get_diameter()

    def get_area(self) -> float:
        return self.c_circle.get().get_area()

    def get_perimeter(self) -> float:
        return self.c_circle.get().get_perimeter()

我最初为每个类单独创建Extension编写setup.py,代码如下:

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

# extension = Extension(
#     "Geometry.Geometry",
#     ["src/Geometry/Geometry.pyx", "../cpp/lib/src/Geometry.cpp"],
#     include_dirs=["../cpp/lib/include"],
#     extra_compile_args=['-std=c++17', '-O3'],
#     language='c++'
# )

extension_segment = Extension(
    "Geometry.Segment",
    ["src/Geometry/Geometry.pyx", "../cpp/lib/src/Geometry.cpp"],
    include_dirs=["../cpp/lib/include"],
    extra_compile_args=['-std=c++17', '-O3'],
    language='c++'
)

extension_angle= Extension(
    "Geometry.Angle",
    ["src/Geometry/Geometry.pyx", "../cpp/lib/src/Geometry.cpp"],
    include_dirs=["../cpp/lib/include"],
    extra_compile_args=['-std=c++17', '-O3'],
    language='c++'
)

extension_circle= Extension(
    "Geometry.Circle",
    ["src/Geometry/Geometry.pyx", "../cpp/lib/src/Geometry.cpp"],
    include_dirs=["../cpp/lib/include"],
    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": ["*.pyx"]},
    ext_modules=cythonize(extension_segment,
                          compiler_directives={'language_level': 3},
                          include_path=["src/Geometry"],
                          annotate=True
                          )+
            cythonize(extension_angle,
                          compiler_directives={'language_level': 3},
                          include_path=["src/Geometry"],
                          annotate=True
                          )+
        cythonize(extension_circle,
                          compiler_directives={'language_level': 3},
                          include_path=["src/Geometry"],
                          annotate=True
                          )
)

测试代码Test.py如下:

from Geometry import Segment, Angle, Circle

class TestGeometry:
    def test_segment_length(self):
        seg = Segment(10.0)
        # assert seg.get_length() == 5.0
        print("HELO")
    #
    # def test_angle_degrees(self):
    #     ang = Angle(90.0)
    #     assert ang.get_degrees() == 90.0
    #
    # def test_angle_radians(self):
    #     ang = Angle(90.0)
    #     assert ang.get_radians() == 1.5707963267948966  # approximately pi/2
    #
    # def test_circle_radius(self):
    #     circ = Circle(3.0)
    #     assert circ.get_radius() == 3.0
    #
    # def test_circle_diameter(self):
    #     circ = Circle(3.0)
    #     assert circ.get_diameter() == 6.0
    #
    # def test_circle_area(self):
    #     circ = Circle(3.0)
    #     assert circ.get_area() == 28.274333882308138  # approximately pi * r^2
    #
    # def test_circle_perimeter(self):
    #     circ = Circle(3.0)
    #     assert circ.get_perimeter() == 18.84955592153876  # approximately 2 * pi * r

运行测试时出现错误:

/home/punreach/Desktop/A_Folder/project-core/venv/bin/python /opt/pycharm-2024.1/plugins/python/helpers/pycharm/_jb_pytest_runner.py --target test_geometry.py::TestGeometry.test_segment_length 
测试于下午6:11开始...
在/home/punreach/Desktop/A_Folder/project-core/lib/Geometry/cython/test目录下,以参数test_geometry.py::TestGeometry::test_segment_length --no-header --no-summary -q启动pytest

============================= 测试会话开始 ==============================
收集测试用例... 
test/test_geometry.py:None (test/test_geometry.py)
test_geometry.py:1: in <module>
    from Geometry import Segment, Angle, Circle
../../../../venv/lib/python3.10/site-packages/Geometry-0.1-py3.10-linux-x86_64.egg/Geometry/__init__.py:1: in <module>
    from .Geometry import *
src/Geometry/Geometry.pyx:1: in init Geometry.Geometry
    ???
E   AttributeError: 模块'Geometry'初始化不完全,没有属性'Segment'(很可能是循环导入导致)
收集到0个用例 / 1个错误

=============================== 1个错误,耗时0.02s ===============================
ERROR: 未找到/home/punreach/Desktop/A_Folder/project-core/lib/Geometry/cython/test/test_geometry.py::TestGeometry.test_segment_length的收集器


进程结束,退出代码4

请问该如何正确编写setup.py解决这个问题?


解决方案

不需要为每个类单独创建Extension,所有类都定义在同一个Geometry.pyx文件中,只需编译成一个扩展模块即可。修改后的setup.py如下:

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

# 仅创建一个Extension,对应整个Geometry模块
extension = Extension(
    "Geometry.Geometry",
    ["src/Geometry/Geometry.pyx", "../cpp/lib/src/Geometry.cpp"],
    include_dirs=["../cpp/lib/include"],
    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": ["*.pyx"]},
    ext_modules=cythonize(
        extension,
        compiler_directives={'language_level': 3},
        include_path=["src/Geometry"],
        annotate=True
    )
)

同时,需要在src/Geometry/__init__.py中显式导出这些类,确保能从Geometry包直接导入:

from .Geometry import Segment, Angle, Circle

__all__ = ["Segment", "Angle", "Circle"]

原因说明
  1. 之前的错误根源是为同一个.pyx文件创建多个Extension,每个Extension试图生成不同的子模块(Geometry.Segment、Geometry.Angle等),但这些子模块本质上编译的是同一个源文件,导致模块初始化时出现冲突和循环依赖。
  2. 正确的做法是将单个.pyx文件编译成一个统一的扩展模块Geometry.Geometry,再通过包的__init__.py导出类,这样就能直接从Geometry包导入所需的类。

内容的提问来源于stack exchange,提问作者Punreach Rany

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 10:05:19