如何创建Python模块分发以优雅地回退到纯Python代码
我写了一个Python模块,现在有两个版本:一个是纯Python实现,另一个是C扩展。我已经写好了__init__.py
文件,它会先尝试导入C扩展,如果失败了,就导入纯Python的代码(这样做合理吗?)。
现在,我想知道怎么才能最好地分发这个模块(比如写一个setup.py
文件),让用户无论有没有能力去构建或使用C扩展,都能轻松使用,只需运行:
python setup.py install
我的经验有限,但我看到有两种可能的情况:
- 用户的电脑上没有安装MS Visual Studio或GCC编译器,无法构建C扩展。
- 用户在使用IronPython、Jython,或者其他不是CPython的环境。我只用过CPython,所以我不太确定如何分发这个模块,让它在那些平台上也能顺利工作并且容易安装,如果他们无法使用C扩展的话。
3 个回答
根据Planar的文档,你可以像往常一样创建一个setup.py
文件来构建C扩展,然后:
如果你想从源代码分发包或仓库中构建并安装Planar,可以使用:
python setup.py install
如果你只想安装纯Python模块,而不进行编译,可以使用:
python setup.py build_py install --skip-build
“尝试导入C语言扩展,如果失败了,就导入纯Python代码(这样合理吗?)”
差不多。你可以了解一下 cStringIO
和 StringIO
。还有 cPickle
和 Pickle
,以及 cElementTree
和 ElementTree
。
如果C版本无法构建,那就是一个使用场景。这时只有纯Python版本可用。
但如果C版本可以构建,我还是有充分的理由拒绝使用它。主要是因为我觉得C版本可能不支持我应用所需的深度子类。
我不想因为刚好有合适的编译器就被迫使用C版本。我更喜欢自己做这些决定。
因此,我不喜欢你的模块中的某些部分替我做架构决策。我更愿意选择导入哪个版本。如果C版本不存在,这并不会改变我的决策过程,因为我可能仍然会创建纯Python版本的子类。
总之,少做自动化。提供这两个模块。我更喜欢自己选择导入哪个。
(这样合理吗?)
没错,非常合理。
为了处理“没有合适的C编译器”的情况:调用 setup(...)
时,如果出现问题,它会直接退出程序。所以,首先可以在 try
语句中尝试使用你想要的 ext_modules
参数:
try:
setup(..., ext_modules=...)
except SystemExit: ...
然后在 except
部分,再次调用 setup(...)
,这次要 不带 ext_modules
(这样就放弃了构建和安装扩展模块)。正在安装的用户仍然会看到类似“无法执行gcc-4.0:没有这样的文件或目录”的消息,但你可以适当地添加自己的消息,告诉用户这没什么大不了的,并且你正在尝试不使用扩展模块的方式。
为了支持非CPython的实现,在你的 setup.py
文件中,你可以检查 sys.version
(我不确定每个非CPython实现的值是什么,但例如IronPython的版本中会有一个 'IronPython'
的子字符串),这样就可以避免尝试 ext_modules
部分。如果在检查中漏掉了一些这样的实现,try/except 语句应该还是能捕捉到大部分其他情况,只是会浪费一点工作量;-)。