问题描述
我有一个 .pyx 文件,我在其中定义了一些函数,例如
cdef double foo(double a) nogil:
return 3. * a
如何在 pyx 文件之外对此类函数的行为进行单元测试? 因为它们是 cdef'd,所以我不能简单地导入它们......
1楼
要测试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)
2楼
尽管前面提到过,最简单的方法是更改cpdef的cdef声明:
cpdef double foo(double a) nogil:
return 3. * a
无需更改任何其他内容。 对于大多数用途,它们实际上是相同的,cpdef 的开销略高,但在继承方面表现更好,请:
指令 cpdef 使该方法的两个版本可用; 一种在 Cython 中使用快,另一种在 Python 中使用较慢。 然后:
这比为 cdef 方法提供 python 包装器的作用略多:与 cdef 方法不同,cpdef 方法完全可以被 Python 子类中的方法和实例属性覆盖。 与 cdef 方法相比,它增加了一点调用开销。