如何将第三方module.pyd打包至内部PyPi并解决命名冲突实现导入?
我有一个供应商提供的module.pyd文件,想推送到内部PyPi仓库,实现pip install module后能直接import module。但这个.pyd有两个限制:
- 无法修改,依赖同目录下的多个dll文件
- 因
PyInit_module定义,不能重命名
我尝试了如下目录结构:
src/ ├─ module/ │ ├─ __init__.py │ ├─ module.pyd │ ├─ other_dll.dll MANIFEST.IN setup.cfg
为了让包名是module(满足pip install module),我在src/module/__init__.py里写了这段代码:
import sys import importlib.util module_package_spec = importlib.util.find_spec("module") package_dir = module_package_spec.submodule_search_locations[0] module_pyd_spec = importlib.util.spec_from_file_location("module", package_dir + "\\module.pyd") module = importlib.util.module_from_spec(module_pyd_spec) sys.modules["module"] = module spec.loader.exec_module(module)
但运行时触发错误:
module = importlib.util.module_from_spec(module_pyd_spec)
SystemError: initialization of module did not return an extension module
我推测是包和模块同名导致的冲突。如果在包外运行这段代码则正常:
import importlib.util module_pyd_spec = importlib.util.spec_from_file_location("module", "<PACKAGE_ABSOLUTE_DIR>\\module.pyd") module = importlib.util.module_from_spec(module_pyd_spec)
另外,手动把.pyd和dll直接放到用户Lib/site-packages目录是可行的,但不知道怎么用setuptools实现,求可行解决办法。
方案1:直接部署到site-packages根目录
通过setuptools的data_files配置,把.pyd和dll直接安装到site-packages根目录,完全避开同名冲突,和手动操作效果一致。
配置文件示例:
- setup.cfg:
[metadata] name = module version = 0.1.0 [options] packages = find: package_dir = = src data_files = = src/module/module.pyd = src/module/other_dll.dll
- MANIFEST.in:
include src/module/*.pyd include src/module/*.dll
执行pip install module后,module.pyd和other_dll.dll会直接出现在Lib/site-packages根目录,直接import module即可正常使用。
方案2:用中转包实现同名导入
把实际包名改成module_wrapper(或其他名称),通过包内的__init__.py完成导入逻辑,让用户依然可以import module。
调整后的目录结构:
src/ ├─ module_wrapper/ │ ├─ __init__.py │ ├─ module.pyd │ ├─ other_dll.dll MANIFEST.IN setup.cfg
配置文件示例:
setup.cfg:
[metadata] name = module version = 0.1.0 [options] packages = find: package_dir = = src package_data = module_wrapper = *.pyd;*.dll
MANIFEST.in:
include src/module_wrapper/*.pyd include src/module_wrapper/*.dll
__init__.py代码:
import sys import os import importlib.util # 获取当前包的目录 package_dir = os.path.dirname(__file__) pyd_path = os.path.join(package_dir, "module.pyd") # 加载.pyd文件并替换sys.modules中的条目 spec = importlib.util.spec_from_file_location("module", pyd_path) module = importlib.util.module_from_spec(spec) sys.modules["module"] = module spec.loader.exec_module(module) # 将dll所在目录加入PATH,确保依赖能被找到 sys.path.insert(0, package_dir)
用户执行pip install module后,实际安装的是module_wrapper包,但可以直接import module,同时dll能被正确加载。
方案3:修复原方案的同名冲突问题
保留原包名module,通过修改__init__.py的加载逻辑,避开包与模块的同名冲突。
修改后的src/module/__init__.py:
import sys import os import importlib.util # 获取当前__init__.py所在的目录 package_dir = os.path.dirname(__file__) pyd_path = os.path.join(package_dir, "module.pyd") # 加载.pyd文件前,先移除sys.modules中的包条目 spec = importlib.util.spec_from_file_location("module", pyd_path) if spec: del sys.modules["module"] module = importlib.util.module_from_spec(spec) sys.modules["module"] = module spec.loader.exec_module(module) # 添加dll目录到PATH sys.path.insert(0, package_dir)
配套配置文件:
setup.cfg:
[metadata] name = module version = 0.1.0 [options] packages = find: package_dir = = src package_data = module = *.pyd;*.dll
MANIFEST.in:
include src/module/*.pyd include src/module/*.dll
这个方案通过先删除sys.modules中的包条目,再加载.pyd模块,解决了同名冲突问题,同时保留了pip install module和import module的使用方式。
内容的提问来源于stack exchange,提问作者Tomass Wilson

