当前位置: 代码迷 >> python >> 如何在 Cython 中测试 cdef 函数?
  详细解决方案

如何在 Cython 中测试 cdef 函数?

热度:77   发布时间:2023-06-16 10:11:57.0

我有一个 .pyx 文件,我在其中定义了一些函数,例如

cdef double foo(double a) nogil:
    return 3. * a

如何在 pyx 文件之外对此类函数的行为进行单元测试? 因为它们是 cdef'd,所以我不能简单地导入它们......

要测试cdef -fuctionality,您需要在 Cython 中编写测试。 可以尝试使用cpdef ,但是在这种情况下并非所有签名都可以使用(例如使用int *float *等指针的签名)。

要访问 cdef 函数,您需要通过 pxd 文件“导出”它们(对于也可以这样做):

#my_module.pyx:
cdef double foo(double a) nogil:
    return 3. * a

#my_module.pxd:
cdef double foo(double a) nogil

现在可以在 Cython 测试器中导入和测试该功能:

#test_my_module.pyx
cimport my_module

def test_foo():
    assert my_module.foo(2.0)==6.0
    print("test ok")

test_foo()

现在

>>> cythonize -i my_module.pyx
>>> cythonize -i test_my_module.pyx 
>>> python -c "import test_my_module"
test ok

从那里开始取决于您的测试基础设施。


例如,如果您使用unittest -module,那么您可以使用 pyximport 对测试模块进行 cythonize/加载 检查它并将所有测试用例转换为unittest -test 用例或直接在您的 cython 代码中使用unittest (可能是更好的解决方案)。

这是unittest的概念证明:

#test_my_module.pyx
cimport my_module
import unittest

class CyTester(unittest.TestCase): 
    def test_foo(self):
        self.assertEqual(my_module.foo(2.0),6.0)

现在,我们只需要翻译并导入它在纯Python能够unittest吧:

#test_cy.py 
import pyximport;
pyximport.install(setup_args = {"script_args" : ["--force"]},
                  language_level=3)

# now drag CyTester into the global namespace, 
# so tests can be discovered by unittest
from test_my_module import *

现在:

>>> python -m unittest test_cy.py
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

顺便说一句,没有必要显式地对 pyx 模块进行 cythonize - pyximport会自动为我们做这件事。

一句警告: pyximport cythonized c 文件缓存在~/.pyxbld (或其他操作系统上的类似文件)中,并且只要test_my_module.pyx没有更改扩展名就不会重建,即使其依赖项发生了变化。 这可能是一个问题(除其他外),当my_module更改并导致二进制不兼容时(幸运的是,如果是这种情况,python 会发出警告)。

通过传递setup_args = {"script_args" : ["--force"]}我们强制重建。

另一种选择是删除缓存文件(可以使用临时目录,例如使用创建,通过 ),这具有保持系统清洁的优点。

需要明确的language_level ( )以防止警告。


如果您使用虚拟环境并通过setup.py (或类似的工作流程)安装 cython-package,您需要,即您的安装文件需要增加:

from setuptools import setup, find_packages, Extension
# usual stuff for cython-modules here
...

kwargs = {
      # usual stuff for cython-modules here
      ...

      #ensure pxd-files:
      'package_data' : { 'my_module': ['*.pxd']},
      'include_package_data' : True,
      'zip_safe' : False  #needed because setuptools are used
}

setup(**kwargs)

尽管前面提到过,最简单的方法是更改cpdefcdef声明:

cpdef double foo(double a) nogil:
    return 3. * a

无需更改任何其他内容。 对于大多数用途,它们实际上是相同的,cpdef 的开销略高,但在继承方面表现更好,请:

指令 cpdef 使该方法的两个版本可用; 一种在 Cython 中使用快,另一种在 Python 中使用较慢。 然后:

这比为 cdef 方法提供 python 包装器的作用略多:与 cdef 方法不同,cpdef 方法完全可以被 Python 子类中的方法和实例属性覆盖。 与 cdef 方法相比,它增加了一点调用开销。