如何在PySpark中记录Python Egg包版本(pkg_resources报DistributionNotFound)
这个问题我在日常工作中碰到过好几次——PySpark集群环境里pkg_resources.get_distribution()抛出DistributionNotFound,但本地Python环境完全正常。核心原因是Spark Executor的Python环境和Driver端的隔离性,以及包分发时元数据可能没被正确同步到Executor节点。下面给你几个实用的解决方案,按推荐程度排序:
1. 硬编码版本号到包的__init__.py(最稳妥)
这是我最推荐的方案,完全避开环境依赖问题。具体操作:
- 在你的包根目录(比如
mypackagename/__init__.py)里添加一行:__version__ = "1.2.3" # 替换成你的实际版本 - 然后在
setup.py里读取这个版本号,避免重复维护:from mypackagename import __version__ setup( name="mypackagename", version=__version__, # 其他配置... ) - 之后在代码里直接导入使用:
from mypackagename import __version__ print(f"当前包版本:{__version__}")
这种方法的优势是不依赖任何包元数据查询工具,不管是Driver还是Executor,只要能导入你的包就能拿到版本号,完全不会有环境适配问题。如果怕手动改版本号麻烦,可以搭配setuptools-scm工具自动从Git标签生成版本号,同步到__init__.py里。
2. 确保包元数据随作业分发
如果你坚持要用pkg_resources或者importlib.metadata,那得保证Executor节点能拿到包的完整元数据:
- 首先,在
setup.py里开启include_package_data=True,确保打包时包含PKG-INFO等元数据文件:setup( # 其他配置... include_package_data=True, ) - 然后提交Spark作业时,不要只依赖集群上预安装的包,而是用
--py-files参数带上你的包的egg/wheel文件:spark-submit --py-files mypackagename-1.2.3-py3-none-any.whl your_job.py
或者通过Spark配置指定:
from pyspark.sql import SparkSession spark = SparkSession.builder \ .config("spark.submit.pyFiles", "mypackagename-1.2.3-py3-none-any.whl") \ .getOrCreate()
这样Executor启动时会自动安装这个包,元数据也会被正确加载,pkg_resources.get_distribution("mypackagename")就能正常工作了。
3. 改用标准库importlib.metadata替代pkg_resources
pkg_resources是setuptools的旧模块,Python 3.8+已经内置了importlib.metadata作为标准库替代,兼容性更好。你可以写一个兼容不同Python版本的版本获取函数:
def get_package_version(package_name): try: from importlib.metadata import version except ImportError: # 针对Python 3.7及以下,需要先安装importlib_metadata包 from importlib_metadata import version return version(package_name) # 使用 print(get_package_version("mypackagename"))
当然,这个方法依然需要确保Executor节点的环境里有包的元数据,所以还是要配合上面提到的包分发方式。
总结一下:如果追求稳定可靠,优先用硬编码版本号的方案;如果必须动态获取版本,那就确保包元数据随作业正确分发,再用importlib.metadata替代老旧的pkg_resources。
内容的提问来源于stack exchange,提问作者Scott Willeke

