在运行时检查 Python 模块版本

许多第三方 Python 模块都有一个属性,该属性包含模块的版本信息(通常类似于 module.VERSIONmodule.__version__) ,但有些则没有。

这类模块的特殊示例有 libxslt 和 libxml2。

我需要检查这些模块的正确版本是否在运行时使用。有办法吗?

一个可能的解决方案是在运行时读取源代码,散列它,然后将其与已知版本的散列进行比较,但这是令人讨厌的。

有更好的解决办法吗?

86448 次浏览

一些想法:

  1. 尝试检查所需版本中存在或不存在的函数。
  2. 如果没有函数差异,请检查函数参数和签名。
  3. 如果无法从函数签名中找到答案,那么在导入时设置一些存根调用并检查它们的行为。

我会远离哈希。正在使用的 libxslt 版本可能包含某种类型的补丁,这种补丁不会影响您对它的使用。

作为替代方案,我建议您不要在运行时进行检查(不知道这是否是一个硬性要求)。对于我编写的具有外部依赖关系的 python 内容(第三方库) ,我编写了一个脚本,用户可以运行该脚本来检查他们的 python 安装是否安装了合适的模块版本。

对于那些没有定义“ version”属性的模块,您可以检查它包含的接口(类和方法) ,看它们是否与预期的接口匹配。然后在您正在编写的实际代码中,假设第三方模块具有您所期望的接口。

从 PyPI 安装的任何东西至少应该有一个版本号。

>>> import pkg_resources
>>> pkg_resources.get_distribution("blogofile").version
'0.7.1'

你可以用

pip freeze

以需求格式查看已安装的包。

如果您使用的是 python>=3.8,那么可以使用内置库中的模块来实现这一点。要检查包的版本(在本例中为 lxml) ,请运行:

>>> from importlib.metadata import version
>>> version('lxml')
'4.3.1'

这个功能已经被移植到旧版本的 python (<3.8)中,但是您需要首先安装一个单独的库:

pip install importlib_metadata

然后检查包的版本(在这个例子中是 lxml)运行:

>>> from importlib_metadata import version
>>> version('lxml')
'4.3.1'

请记住,这只适用于从 PyPI 安装的包。另外,必须将包名作为参数传递给 version方法,而不是该包提供的模块名(尽管它们通常是相同的)。

我发现使用各种可用的工具(包括 另一个答案提到的最好的 pkg_resources)是相当不可靠的,因为它们中的大多数并不涵盖所有情况。比如说

  • 内置模块
  • 模块没有安装,只是添加到 python 路径(例如,通过 IDE)
  • 同一模块有两个版本可用(一个在 python 路径中,一个在已安装的路径中)

因为我们需要一种可靠的方法来获得 任何包、模块或子模块的版本,所以我最终编写了 获得版本。它的使用非常简单:

from getversion import get_module_version
import foo
version, details = get_module_version(foo)

有关详细信息,请参阅 文件

对于不提供 __version__的模块,以下内容虽然难看,但是可以使用:

#!/usr/bin/env python3.6
import sys
import os
import subprocess
import re


sp = subprocess.run(["pip3", "show", "numpy"], stdout=subprocess.PIPE)
ver = sp.stdout.decode('utf-8').strip().split('\n')[1]
res = re.search('^Version:\ (.*)$', ver)
print(res.group(1))

或者

#!/usr/bin/env python3.7
import sys
import os
import subprocess
import re


sp = subprocess.run(["pip3", "show", "numpy"], capture_output=True)
ver = sp.stdout.decode('utf-8').strip().split('\n')[1]
res = re.search('^Version:\ (.*)$', ver)
print(res.group(1))