如何检测Python包是否从源码树内导入?兼容Python2/3
检测Python包是否从源码树导入的稳健方案
我来分享一个简洁、高效且兼容Python 2/3的方案,完美解决你提到的痛点,同时避开之前思路里的各种问题:
核心思路
安装后的包路径和源码树里的包路径存在本质差异:
- 安装后的包会位于
site-packages/dist-packages这类标准目录下 - 源码树里的包目录,上级通常存在项目的
setup.py等配置文件
我们可以结合已安装包路径对比和源码特征文件检查,只在模块首次导入时执行一次检测,避免重复开销。
实现代码
在你的Foo/__init__.py中添加以下代码:
import os import sys # 模块级变量,存储检测结果(仅首次导入时计算一次) _IS_SOURCE_TREE = False # 获取当前包的绝对路径 pkg_dir = os.path.abspath(os.path.dirname(__file__)) try: # 利用pkg_resources获取已安装包的位置(兼容py2/3,依赖setuptools,安装包必然存在) import pkg_resources dist = pkg_resources.get_distribution('Foo') installed_root = os.path.abspath(dist.location) # 如果当前包目录不在已安装路径下,判定为源码树导入 if not pkg_dir.startswith(os.path.join(installed_root, 'Foo')): _IS_SOURCE_TREE = True except pkg_resources.DistributionNotFound: # 包未安装时,检查上级目录是否存在setup.py(源码树特征) parent_dir = os.path.dirname(pkg_dir) if os.path.exists(os.path.join(parent_dir, 'setup.py')): _IS_SOURCE_TREE = True # 触发警告(或根据需求抛出错误) if _IS_SOURCE_TREE: import warnings warnings.warn( "⚠️ 你正在从源码树导入Foo,请先通过pip安装或从源码编译安装后再使用。", UserWarning, stacklevel=2 # 让警告指向用户的import语句,而非本代码 )
方案优势
- 高效低开销:仅在模块首次导入时执行一次检测,结果存在模块变量中,后续import无需重复计算;
pkg_resources.get_distribution会利用缓存,性能损耗可忽略。 - 稳健防误判:
- 优先通过已安装路径对比判断,避免了“有人在site-packages放setup.py”的误判场景
- 仅在包未安装时才检查
setup.py,兼顾了纯源码导入的场景
- 兼容广泛:完美支持Python 2和3,依赖的
setuptools是Python包安装的标准组件,不存在额外依赖问题 - 优雅无侵入:不需要创建虚拟标记文件,也不需要在安装时修改源码,保持项目结构纯净
可选优化
如果你不想依赖pkg_resources,可以替换为检查当前包路径是否在sys.path的标准安装目录中:
# 替代pkg_resources的逻辑 _IS_INSTALLED = any( pkg_dir.startswith(os.path.abspath(path)) for path in sys.path if 'site-packages' in path or 'dist-packages' in path ) _IS_SOURCE_TREE = not _IS_INSTALLED and os.path.exists(os.path.join(os.path.dirname(pkg_dir), 'setup.py'))
不过这种方式的准确性略低于pkg_resources,因为无法精准匹配当前包的安装位置,但对于大部分场景已经足够。
内容的提问来源于stack exchange,提问作者Chris_Rands




