У меня есть.pyx файл, в котором я определяю некоторые функции, например
cdef double foo(double a) nogil:
return 3. * a
Как я могу тестировать поведение таких функций вне файла pyx? Поскольку они cdef'd, я не могу просто импортировать их...
У меня есть.pyx файл, в котором я определяю некоторые функции, например
cdef double foo(double a) nogil:
return 3. * a
Как я могу тестировать поведение таких функций вне файла pyx? Поскольку они cdef'd, я не могу просто импортировать их...
Для тестирования cdef
-fuctionality вам нужно написать свои тесты на Cython. Можно попытаться использовать cpdef
-functions, однако не все подписи могут быть использованы в этом случае (например, подписи с использованием указателей, таких как int *
, float *
и т.д.).
Для доступа к cdef -functions вам необходимо "экспортировать" их через pxd файл:
#my_module.pyx:
cdef double foo(double a) nogil:
return 3. * a
#my_module.pxd:
cdef double foo(double a) nogil
Теперь функциональность может быть импортирована и протестирована на Cython -test er:
#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, чтобы цитонизировать/загрузить тест -module, проверить его и преобразовать все тестовые случаи в unittest
-test или использовать unittest
непосредственно в вашем цитоне. код (возможно, лучшее решение).
Вот подтверждение концепции для unittest
:
#test_my_module.pyx
cimport my_module
import unittest
class CytTester(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)
from test_my_module import *
import unittest
А теперь:
>>> python -m unittest test_cy.py
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Кстати, нет необходимости явно цифонизировать pyx -module s - pyximport
делает это для нас автоматически.
Предупреждение: pyximport
кэширует c файлы, содержащие цитаты, в ~/.pyxbld
(или аналогичные в других ОС) и до тех пор, пока test_my_module.pyx
не изменился, расширение не перестраивается, даже если его зависимости были изменены, Это может быть проблемой (среди прочих), когда изменяется my_module
и это приводит к двоичной несовместимости (к счастью, python предупреждает, если это так).
Проходя setup_args = {"script_args" : ["--force"]}
, мы форсируем перестройку.
Другой вариант - удалить кэшированные файлы (можно использовать временный каталог, например, созданный с помощью tempfile.TemporaryDirectory()
, через pyximport.install(build_dir=...)
), что позволяет сохранить система чистая.
Явное language_level
(что такое language_level
?) необходимо для предотвращения предупреждений.
Если вы используете виртуальную среду и устанавливаете свой cython-пакет с помощью setup.py
(или аналогичного рабочего процесса), вам необходимо , чтобы убедиться, что файлы *.pxd
также включены в установочный файл, то есть ваш установочный файл должен быть дополнен:
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)