如何用importlib导入setuptools安装的含下划线Python包
问题
用setuptools创建名称含下划线的Python包时,下划线会被自动替换为横杠(比如foo_bar变成foo-bar),但自动导入时遇到问题:pkg_resources.working_set返回的是带横杠的名称,用这个名称调用importlib.import_module()会报错ModuleNotFoundError,但手动用下划线版本import foo_bar可以正常工作。
包结构:
foo_bar ├─ foo_bar | ├─ __init__.py | ├─ foo_bar.py └─ setup.py
执行开发安装命令:
python3 setup.py develop --user
pip3 list输出:
pip3 list | grep foo foo-bar 0.1 /home/XYZ/foo_bar
自动导入报错代码及输出:
import pkg_resources import importlib for d in pkg_resources.working_set: if('foo' in d.project_name): print(d.location, d.project_name, d.version, d.key) libstr = d.key print('Importing lib',libstr) testlib = importlib.import_module(libstr)
输出:
/home/XYZ/foo_bar foo-bar 0.1 foo-bar Importing lib foo-bar Traceback (most recent call last): File "/home/XYZ/foo_bar/test/import_foo_bar.py", line 10, in <module> testlib = importlib.import_module(libstr) File "/usr/lib/python3.10/importlib/__init__.py", line 126, in import_module return _bootstrap._gcd_import(name[level:], package, level) File "<frozen importlib._bootstrap>", line 1050, in _gcd_import File "<frozen importlib._bootstrap>", line 1027, in _find_and_load File "<frozen importlib._bootstrap>", line 1004, in _find_and_load_unlocked ModuleNotFoundError: No module named 'foo-bar'
手动导入正常:
In [1]: import foo_bar In [2]: foo_bar.foo_bar Out[2]: <module 'foo_bar.foo_bar' from '/home/XYZ/foo_bar/foo_bar/foo_bar.py'>
解决方案
方法1:从包元数据读取真实导入名
pkg_resources的Distribution对象可以获取包的top_level.txt内容,这个文件记录了可导入的顶级模块名(即下划线版本的名称)。修改代码如下:
import pkg_resources import importlib for d in pkg_resources.working_set: if 'foo' in d.project_name: print(d.location, d.project_name, d.version, d.key) # 获取顶级模块名 top_levels = d.get_metadata('top_level.txt').strip().split('\n') for module_name in top_levels: if module_name: # 跳过空行 print('Importing lib', module_name) testlib = importlib.import_module(module_name) print(f'Successfully imported {module_name}')
方法2:手动转换横杠为下划线
如果确定包名的横杠都是由下划线转换而来,直接将d.key中的横杠替换为下划线即可:
import pkg_resources import importlib for d in pkg_resources.working_set: if 'foo' in d.project_name: print(d.location, d.project_name, d.version, d.key) libstr = d.key.replace('-', '_') print('Importing lib', libstr) testlib = importlib.import_module(libstr)
这种方法简单直接,但需确保包的导入名和项目名的转换规则一致。
方法3:在setup.py中明确指定packages参数
确保setup.py里的packages参数正确指向可导入的模块名,避免元数据生成错误:
from setuptools import setup, find_packages setup( name="foo-bar", # PyPI显示的项目名称 version="0.1", packages=find_packages(), # 自动发现foo_bar模块 # 也可手动指定:packages=["foo_bar"], )
配置后top_level.txt会正确生成foo_bar,后续通过元数据获取的导入名就是正确的。
内容的提问来源于stack exchange,提问作者pholter
相关产品推荐
相关产品推荐

