Python项目中同一第三方库多版本适配代码的优雅管理方案咨询
我正在开发一个Python项目,需要支持同一第三方库的多个版本。每新增一个版本,我们的部分包装类会发生变化,另一部分则和上一版本保持一致。目前已经有三个版本需要支持,明年还会新增第四个版本,我希望能优雅地兼容所有版本。
为了说明问题,我做了一个极简示例,项目结构如下:
. ├── pyproject.toml └── src └── version_example ├── __init__.py ├── bar │ ├── __init__.py │ ├── bar_1.py │ └── bar_2.py ├── baz │ ├── __init__.py │ ├── baz_1.py │ ├── baz_2.py │ └── baz_3.py ├── foo │ ├── __init__.py │ └── foo.py └── main.py
我的pyproject.toml内容如下:
[tool.poetry] name = "version_example" version = "0.0.1" description = "" authors = [] packages = [{ include = "version_example", from = "src" }]
main.py的内容是:
import os from version_example.foo.foo import Foo if int(os.environ["TARGET_VERSION"]) == 1: from version_example.bar.bar_1 import Bar else: from version_example.bar.bar_2 import Bar if int(os.environ["TARGET_VERSION"]) == 1: from version_example.baz.baz_1 import Baz elif int(os.environ["TARGET_VERSION"]) == 2: from version_example.baz.baz_2 import Baz else: from version_example.baz.baz_3 import Baz if __name__ == "__main__": foo = Foo() foo() bar = Bar() bar() baz = Baz() baz()
每个类都是可调用对象,调用时会打印自身版本信息,比如version_example.bar.bar_2的代码:
class Bar: def __call__(self): print("Bar version 2")
这种方式显然无法适配未来新增的版本,扩展性很差。
我希望能让调用代码完全不需要关心版本信息,比如实现这样的调用方式:
# main.py from version_pecific.foo import Foo from version_pecific.bar import Bar from version_pecific.baz import Baz if __name__ == "__main__": foo = Foo() foo() bar = Bar() bar() baz = Baz() baz()
我想到了两种方案,但都有明显缺点:
方案一:拆分多版本独立模块
把项目拆分成多个独立的版本模块,结构如下:
. ├── poetry.lock ├── pyproject.toml ├── src │ └── version_example │ ├── __init__.py │ └── main.py └── versions ├── version_1 │ └── version_pecific │ ├── __init__.py │ ├── bar.py │ ├── baz.py │ └── foo.py ├── version_2 │ └── version_pecific │ ├── __init__.py │ ├── bar.py │ ├── baz.py │ └── foo.py └── version_3 └── version_pecific ├── __init__.py ├── bar.py ├── baz.py └── foo.py
这种方式可以通过设置PYTHONPATH来加载对应版本的模块,比如:
export PYTHONPATH=./versions/version_3:$PYTHONPATH
但缺点也很明显:大量代码重复,比如bar.py在version_2和version_3中完全相同,foo.py在所有版本模块中都是重复的。
方案二:将版本判断移至__init__.py
把版本判断逻辑放到各个子模块的__init__.py中,比如src/version_example/bar/__init__.py:
import os if int(os.environ["TARGET_VERSION"]) == 1: from version_example.bar.bar_1 import Bar else: from version_example.bar.bar_2 import Bar
src/version_example/baz/__init__.py:
import os if int(os.environ["TARGET_VERSION"]) == 1: from version_example.baz.baz_1 import Baz elif int(os.environ["TARGET_VERSION"]) == 2: from version_example.baz.baz_2 import Baz else: from version_example.baz.baz_3 import Baz
这种方式不需要复制代码,调用端也不用关心版本,但增加了一层间接性——阅读代码追踪导入时,必须时刻注意TARGET_VERSION的取值,对代码可读性和调试不太友好。
所以我想请教:能不能用Poetry或者其他Python构建工具,把版本相关的代码打包成独立包,并且让每个版本包依赖更低版本的包,这样每个版本包可以决定是继承上一版本的类还是重写?
我觉得这应该是支持多版本第三方库的通用问题,但一直没找到满意的解决方案。任何思路或者关键词提示都非常感谢!
备注:内容来源于stack exchange,提问作者Arnór

