Cythonization 错误至少有四种解决方案(这些结果带有cython == 0.29.24
):
添加文件example_package/__init__.pxd
并将正在构建的 s 的名称更改为Extension
正在构建的模块的子模块,即example_package.other
和example_package.driver
(在问题中这些将是Test.other
和Test.driver
)。
无论如何,此更改对于导入已安装的子模块driver
和都是必需的other
,如下所述。请注意,由于缺少关键字 parameter 和 argument ,在这种情况下安装的包实际上是一个命名空间包packages=['example_package']
,如下所述。
添加文件example_package/__init__.py
并将正在构建的 s 的名称更改为Extension
正在构建的模块的子模块,即example_package.other
和example_package.driver
. 即使在这种情况下,如果__init__.py
存在 an,安装的包example_package
也将是一个命名空间包。把它变成一个普通的包需要传递packages=['example_package']
给函数setuptools.setup
。
与添加 类似__init__.pxd
,此更改对于导入已安装的子模块是必要的。
添加文件example_package/__init__.pxd
并将语句更改为文件内cimport
的绝对值(使用此替代方法构建和安装包,但不导入,因为还需要更改s 的名称):cimport
example_package/driver.pyx
Extension
from . import other
from example_package cimport other
添加文件example_package/__init__.py
并将语句更改为文件内cimport
的绝对值,如上一项中所做的那样。该软件包以此构建和安装,但不导入。cimport
example_package/driver.pyx
问题是明确要求相对进口,所以从这个意义上说,前两个备选方案是问题的答案,因为它们确实适用于相对进口。
上面列出的四个更改中的任何一个都可以避免以下错误:
Error compiling Cython file:
------------------------------------------------------------
...
from . import other
from . cimport other
^
------------------------------------------------------------
example_package/driver.pyx:2:0: relative cimport beyond main package is not allowed
但正如上面已经提到的,也将在下面讨论,第一个或第二个替代方案的名称的更改对于导入已安装的子模块是必要的(另外在第四个替代方案中Extension
传递参数和关键字参数允许 Python 包导入,但不是它的子模块和)。packages=[PACKAGE_NAME]
example_package
driver
other
修改的setup.py
我推荐的文件setup.py
以及所有其他更改(不仅是上面列出的构建和安装所需的更改)是:
"""Installation script."""
import os
import setuptools
try:
from Cython.Build import cythonize
cy_ext = f'{os.extsep}pyx'
except ImportError:
# this case is intended for use when installing from
# a source distribution (produced with `sdist`),
# which, as recommended by Cython documentation,
# should include the generated `*.c` files,
# in order to enable installation in absence of `cython`
print('`import cython` failed')
cy_ext = f'{os.extsep}c'
PACKAGE_NAME = 'example_package'
def run_setup():
"""Build and install package."""
ext_modules = extensions()
setuptools.setup(
name=PACKAGE_NAME,
ext_modules=ext_modules,
packages=[PACKAGE_NAME],
package_dir={PACKAGE_NAME: PACKAGE_NAME})
def extensions():
"""Return C extensions, cythonize as needed."""
extensions = dict(
other=setuptools.extension.Extension(
f'{PACKAGE_NAME}.other',
sources=[f'{PACKAGE_NAME}/other{cy_ext}'],),
driver=setuptools.extension.Extension(
f'{PACKAGE_NAME}.driver',
sources=[f'{PACKAGE_NAME}/driver{cy_ext}'],))
if cy_ext == f'{os.extsep}pyx':
ext_modules = list()
for k, v in extensions.items():
c = cythonize(
[v],
# show_all_warnings=True # this line requires `cython >= 3.0`
)
ext_modules.append(c[0])
else:
ext_modules = list(extensions.values())
return ext_modules
if __name__ == '__main__':
run_setup()
其他变化
此答案中的其他更改对于成功构建和安装软件包不是必需的,但出于其他原因推荐。对于其他一些变化,我在下面描述了动机。
注意:
- 仅添加
example_package/__init__.pxd
或example_package/__init__.py
不足,并且
- 仅更改
Extension
名称是不够的,并且
- 仅将
cimport
语句更改为from example_package cimport other
是不够的。
构建和安装需要这些更改中的两个,即前面列出的四个替代方案之一。
为了能够导入从 Cython 源构建的扩展模块driver.pyx
,other.pyx
还需要将扩展名更改为:
Extension('example_package.other', ...)
Extension('example_package.driver', ...)
请注意,这是import
可行的,因为现在example_package
已成为命名空间包(CPython词汇表条目):
>>>
<module 'example_package' (namespace)>
>>> import example_package.driver
>>> import example_package.other
(另外,我省略了我使用的文件中的参数,我将其包含在下面。include_dirs
)setuptools.setup
setup.py
构建和安装包以及导入扩展模块需要这些更改。用于从 Python导入已安装的包,以防它不包含任何扩展模块(因此没有成为命名空间包):
__init__.py
需要在目录中添加一个文件example_package/
(问题是目录Test/
),并且
- 关键字参数
packages=[
example_package],
需要传递给函数setuptools.setup
。
否则,该语句import example_package
将引发ModuleNotFoundError
. 添加__init__.py
文件也是必要的,以使包成为常规包(CPython词汇表条目),这通常是预期的,而不是命名空间包。
是否使用__init__.pxd
一个常规的 Python 包包含一个__init__.py
文件。文件仅在其他包需要标头的__init__.pxd
情况下才相关*.pxd
。如果不是这种情况,文件似乎example_package/__init__.py
就足够了,因为上面的四个解决方案本质上是两个解决方案,每个解决方案都有一个__init__.py
或__init__.pxd
作为替代方案。
所以我对文件及其安排的建议是:
.
├── example_package
│ ├── __init__.py
│ ├── driver.pyx
│ ├── other.pxd
│ └── other.pyx
└── setup.py
两项更改都需要
仅添加__init__.pxd
文件会引发 cythonization 错误:
Error compiling Cython file:
------------------------------------------------------------
...
from . import other
from . cimport other
^
------------------------------------------------------------
example_package/driver.pyx:3:0: relative cimport beyond main package is not allowed
并且仅更改cimport
语句(没有__init__.pxd
)会引发 cythonization 错误:
Error compiling Cython file:
------------------------------------------------------------
...
#!/usr/bin/env python
from . import other
from example_package cimport other
^
------------------------------------------------------------
example_package/driver.pyx:3:0: 'example_package.pxd' not found
Error compiling Cython file:
------------------------------------------------------------
...
#!/usr/bin/env python
from . import other
from example_package cimport other
^
------------------------------------------------------------
example_package/driver.pyx:3:0: 'example_package/other.pxd' not found
命名包
上面我写example_package
的是包的名称,尽管我确实使用Test/
问题中命名的名称构建和安装了示例,以确保这确实有效,并且所需的最小更改是__init__.pxd
文件和from example_package cimport other
.
为了统一起见,我其实在使用这个包名Test/
运行的时候也将目录重命名为在问题中,会导致区分大小写的文件系统出现问题。setup.py
test/
name='Test',
setup.py
所以:
- 使用
Test
作为包名称和Test
目录名称为我构建和安装工作,并且
- 使用
test
作为包名称和test
目录名称为我构建和安装工作。
我建议使用另一个包名称。此外,出于以下原因:
- 在包命名时导入
Test
是使用语句完成的import Test
。写作import test
将导入另一个包(见下文)。
- using
test
as package name 不会导入已安装的test
包,原因如下所述,即使__init__.py
添加了文件也是如此。
在任何情况下,出于下面解释的原因,我的建议是更改包名称,即使它旨在成为仅用作主包的测试工具的辅助包。
此外,PEP 8强制要求小写包命名,因此导致test
,它可以被理解为测试目录,如果这实际上是作为主包的示例,则情况并非如此。
构建和安装后发生的错误,当包和目录被命名时test
(点...是编辑实际输出的结果):
>>> import test
>>> test
<module 'test' from '.../lib/python3.9/test/__init__.py'>
换句话说,CPython 包含一个名为的包test
:
该test
包包含 Python 的所有回归测试以及模块test.support
和test.regrtest
.
因此,该名称test
不能用于打算在安装后导入的示例包(尽管包确实被构建和安装,甚至被卸载pip uninstall -y test
,很好)。
另一个细节from test cimport other
实际上是错误的,即使它可以编译,因为如果构建的test
包实际上以某种方式被神奇地导入(在存在 CPython 的test
包的情况下),在运行时该cimport
语句将默认为 CPython 的test
包。尽管如此,Cython 的翻译可能会将其转换为实际从构建包中导入的cimport
其他形式。test.other
由于test
在存在 CPython 包的情况下似乎无法导入已安装的test
包,因此不知道这是否cimport
会引发运行时错误。
另外,请注意:
注意:该test
包仅供 Python 内部使用。它的文档是为了 Python 的核心开发人员的利益。不鼓励在 Python 标准库之外使用此包,因为此处提到的代码可能会在 Python 版本之间更改或删除,恕不另行通知。
在所有实验之间,我运行rm -rf build dist *.egg-info test/*.c
. 因此,在将文件排列更改为之前显示的文件排列之前,我使用的与问题相同。
将包重命名为example_package
我将包的名称更改为example_package
,假设它test/
包含要安装的实际包,基于问题name=
文件setup.py
中提供给参数的参数。
重命名的动机是“test”或“tests”通常用于命名伴随 Python 包的测试目录。此类目录以及如何使用测试有很多安排。在下一节中,我将讨论我对安排测试的建议。
关于可能性,我在下一节中描述的安排以外的安排通常被使用,包括将测试放在包本身的目录中。鉴于问题是写的myProject/
,并且有一个文件myProject/__init__.py
,我不确定问题是否真的使用了这种安排。
但是,在这种情况下,driver
实际上other
是测试模块。尽管将测试安装为一个单独的包(Test
在问题中称为),这就是模块myProject/setup.py
所做的,但这表明driver
并且other
是主包的模块,因此主包被称为“测试”。
如果不是,即如果driver
和other
实际上是测试模块,并且setup.py
不是主包的设置脚本,而是构建和安装“辅助”包的设置脚本,该包仅用于测试主包(在这种情况下可能是命名为“myProject”,在包含问题目录的目录中存在),那么我对 to 的重命名将不对应于这是主包。(拥有一个包含 Cython 代码的测试工具包也很有趣,因此需要编译 - 并且可能安装。)setup.py
myProject/
Test
example_package/
在这种情况下,也许Test
可以改名为tests_of_example_package
. 换句话说,在这种情况下,在包的名称中包含单词“test”是相关的,尽管似乎将包限定为辅助example_package
是明确的。显式优于隐式(PEP 20)。
(测试有时被安排为包(使用__init__.py
),即使没有将其安装为辅助 Python 包(仅作为它随附的主要 Python 包的测试工具)。动机是启用导入测试套件的通用模块被多个测试模块使用,但它们本身并不是由测试运行器直接运行的模块。)
如果这是主包,那么我假设“测试”是为了在示例中编写示例。如果是这样,那么我重命名(小写除外)的唯一原因是将主包本身与其测试区分开来。
PEP 8要求 Python 包的小写名称:
Python 包也应该有简短的全小写名称,尽管不鼓励使用下划线。
中的下划线example_package
仅用于示例。
安排测试
测试可能放置在test/
与包含 Python 包的目录位于同一目录中的目录中,并以包命名。我强烈推荐这种方法,例如(这棵树是用程序创建的tree
):
.
├── example_package
│ └── __init__.py
├── setup.py
└── tests
└── module_name_test.py
为了在不意外从源目录导入包的情况下进行测试,但从它example_package
的安装位置(通常在. 这是最可靠的测试方法,不依赖于每个测试框架如何工作,测试框架的各种配置选项如何工作,选项如何相互交互,也不依赖于测试框架本身的错误如何影响测试。site-packages/
cd
tests/
这样,包源就可以放在目录里面了example_package
,没有任何理由使用任何其他的目录排列方式。
Python 模块中的 Shebangs
文件里面的shebang可以*.pyx
去掉,因为没有效果。shebang 行被Cython*.c
视为 Python 注释行,稍后移到 Cython 从文件生成的文件中的某个 C 注释内*.pyx
。所以它没有效果。我不知道在 C 源代码中使用 shebang 行,这些代码旨在通过直接调用gcc
(或另一个 C 编译器)进行编译,就像 Cython 所做的那样(无论是 Cython 调用gcc
,还是另一个编译器取决于系统、环境路径、环境变量和其他信息)。
此外,shebang 仅与可能作为可执行文件执行的 Python 模块相关。这不适用于 Python 包中的模块,因此几乎从不使用 shebang 行。
一个例外可能是在开发过程中可能很少直接运行的包模块,例如,用于实验或调试目的。尽管如此,这样的模块应该有一个__main__
节。
因此,与 shebang 相关的 Python 模块通常也有一个__main__
节。
为了完整起见,setup.py
旨在作为 运行__main__
,并且确实有一个__main__
节,但是设置脚本的运行方式(强烈建议不使用pip
--using时pip
)是 by python setup.py
,因此不需要 shebang in setup.py
(没有 shebang 出现在问题中-我只是为了完整性而提到这一点)。
distutils
模块]()自Python 3.10 起已弃用,如PEP 632中所述,并将在 Python 3.12 中删除。
当 Cython 不存在时切换到扩展.c
,而不是.pyx
这符合Cython 的建议:
强烈建议您分发生成的.c
文件以及 Cython 源代码,以便用户可以安装您的模块,而无需 Cython 可用。
模块范围变量的大写名称setup.py
保持不变(“常量”)
旨在用作常量的模块范围 Python 变量,即在初始赋值后保持不变,PEP 8强制要求具有带下划线的大写标识符:
常量通常在模块级别定义,并以全大写字母书写,并用下划线分隔单词。示例包括MAX_OVERFLOW
和TOTAL
。
因此标识符PACKAGE_NAME
。
我使用了格式化字符串文字,这需要 Python >= 3.6。
将代码安排为模块内的函数setup.py
这通常是一种很好的做法,允许通过函数名称命名不同的代码部分,仅在运行时执行代码__main__
,通过包含一个__main__
节,从而允许导入setup.py
和使用可能与外部代码相关的特定功能(例如,安装框架),而不必运行所有代码——例如,不运行函数setuptools.setup
。
该问题提供了一个最小的工作示例,因此setup.py
与该问题相关的是一个小示例。我写这部分是为了建议在实际包中做什么,而不是在问题中。
同样的观察适用于内部的模块和函数文档字符串setup.py
。
另外,我推荐函数的自顶向下排列:调用者在被调用者之上,因为这种布局更具可读性。
我用于os.extsep
一般性,虽然使用点我认为仍然可以工作,并且更具可读性。
包裹安排
正如我之前提到的,为避免构建错误“不允许超出主包的相对 cimport”所需的问题示例的唯一更改是添加 an__init__.py
或 an __init__.pxd
,以及绝对cimport
内部driver.pyx
或重命名Extensions
.
删除文件__init__.py
在最终版本中,我删除了__init__.py
与setup.py
. 我的理解是这个文件在这个例子中没有任何作用。如果该示例旨在test/
作为主包的目录,那么 any__init__.py
将出现在test/
.
如果test/
实际上是主包的测试辅助包,那么__init__.py
它将是主包的一部分,与包无关test/
。但是,在这种情况下,似乎setup.py
上面会有一个文件myProject/
,它负责构建主包和测试工具包。
使用绝对导入
language_level
in的默认cython < 3.0.0
值为 2,即使在 Python 3 上也是如此:
language_level
(2/3/3str)
全局设置用于模块编译的 Python 语言级别。默认与 Python 2 兼容。要启用 Python 3 源代码语义,请在模块开头将其设置为 3(或 3str)或将“-3”或“--3str”命令行选项传递给编译器。
该问题使用 Python 3.5 和cython == 0.23.4
,所以情况就是这样。
默认的 Cython 语义正在发生变化cython >= 3.0.0
:
默认语言级别更改为3str
,即 Python 3 语义,...
使用 Python 2 和 Python 3 语义(传递compiler_directives=dict(language_level=3)
或安装 pre-release cython == 3.0.0a8
),前两个解决方案(使用相对导入)确实有效。
尽管如此,PEP 8 建议绝对导入:
建议使用绝对导入,因为如果导入系统配置不正确,它们通常更具可读性并且往往表现得更好(或至少给出更好的错误消息)......
绝对导入对于重构包的结构也很健壮。它们是显式的,显式优于隐式(PEP 20)。
此更改后生成的模块driver.pyx
将是:
from example_package import other
from example_package cimport other
此答案中的setup.py
代码基于我在Python 包download.py
文件中编写的内容。dd