Python模块的依赖树
通常,许多Python包都依赖于其他包,但我们如何知道模块依赖于哪些包?
点冻结:
这是一个Python内置模块,可以帮助我们了解依赖包,但它将所有依赖项显示为一个平面列表,找出哪些是顶级包以及它们依赖哪些包需要一些努力。让我们看一个例子来说明它是如何工作的:
在命令提示符下键入以下命令:
$pip freeze
输出:
altair==4.1.0
attrs==19.3.0
docutils==0.15.2
entrypoints==0.3
Jinja2==2.11.2
jmespath==0.10.0
jsonschema==3.2.0
MarkupSafe==1.1.1
numpy==1.18.4
opencv-python==4.2.0.34
pandas==1.0.4
pyrsistent==0.16.0
python-dateutil==2.8.1
pytz==2020.1
six==1.15.0
toolz==0.10.0
urllib3==1.25.9
pipdeptree 实用程序:
一种简单的方法是使用pipdeptree 实用程序。 pipdeptree 在命令行上工作,并以依赖树的形式显示已安装的Python包。
这个模块没有内置在Python中。要安装它,请在终端中键入以下命令。
$pip install pipdeptree
这将安装至少需要Python 2.7 的最新版本的 pipdeptree。
现在在命令提示符下运行此命令以获取所有Python模块的依赖关系树。
命令:
$pipdeptree
输出:
$pipdeptree
altair==4.1.0
- entrypoints [required: Any, installed: 0.3]
- jinja2 [required: Any, installed: 2.11.2]
- MarkupSafe [required: >=0.23, installed: 1.1.1]
- jsonschema [required: Any, installed: 3.2.0]
- attrs [required: >=17.4.0, installed: 19.3.0]
- pyrsistent [required: >=0.14.0, installed: 0.16.0]
- six [required: Any, installed: 1.15.0]
- setuptools [required: Any, installed: 41.2.0]
- six [required: >=1.11.0, installed: 1.15.0]
- numpy [required: Any, installed: 1.18.4]
- pandas [required: >=0.18, installed: 1.0.4]
- numpy [required: >=1.13.3, installed: 1.18.4]
- python-dateutil [required: >=2.6.1, installed: 2.8.1]
- six [required: >=1.5, installed: 1.15.0]
- pytz [required: >=2017.2, installed: 2020.1]
- toolz [required: Any, installed: 0.10.0]
docutils==0.15.2
jmespath==0.10.0
opencv-python==4.2.0.34
- numpy [required: >=1.17.3, installed: 1.18.4]
pipdeptree==1.0.0
- pip [required: >=6.0.0, installed: 20.2.1]
urllib3==1.25.9
结合 pipdeptree 和 freeze:
让我们看看当我们使用 pipdeptree 并完全冻结时会发生什么,
命令:
$pipdeptree --freeze
输出:
altair==4.1.0
entrypoints==0.3
Jinja2==2.11.2
MarkupSafe==1.1.1
jsonschema==3.2.0
attrs==19.3.0
pyrsistent==0.16.0
six==1.15.0
setuptools==41.2.0
six==1.15.0
numpy==1.18.4
pandas==1.0.4
numpy==1.18.4
python-dateutil==2.8.1
six==1.15.0
pytz==2020.1
toolz==0.10.0
docutils==0.15.2
jmespath==0.10.0
Mako==1.1.3
MarkupSafe==1.1.1
opencv-python==4.2.0.34
numpy==1.18.4
pipdeptree==1.0.0
pip==20.2.1
scipy==1.5.2
numpy==1.18.4
urllib3==1.25.9
因此,在这里我们看到使用 pipdeptree 和 freeze 通过组合两个命令的属性来显示输出。因此,看起来 pip freeze 的输出指示了该软件包安装了哪个软件包,类似于 pipdeptree 但这里使用缩进而不是连字符(-)来表示树。
pipdeptree 中的警告:
执行pipdeptree命令时通常会出现两种警告,让我们一一查看。
1. 冲突依赖:顾名思义“冲突依赖”,它的相关性也是如此。有时,某些包被指定为具有不同版本的多个包的依赖项,在这种情况下,可能会出现冲突依赖警告。因此,任何被指定为具有不同版本的多个包的依赖项的包都被视为可能的冲突依赖项。
默认情况下,pipdeptree 会警告可能存在冲突的依赖项。
让我们再看一个 pipdeptree 的例子:
命令:
$pipdeptree
输出:
Warning!!! Possibly conflicting dependencies found:
* impacket==0.9.20
- ldap3 [required: ==2.5.1, installed: ?]
- ldapdomaindump [required: >=0.9.0, installed: ?]
------------------------------------------------------------------------
alembic==1.0.11.dev0
attrs==18.2.0
dulwich==0.20.2
- certifi [required: Any, installed: 2018.11.29]
- urllib3 [required: >=1.24.1, installed: 1.24.1]
EditorConfig==0.12.1
Flask-Cors==3.0.8
- Flask [required: >=0.9, installed: 1.1.1]
- Six [required: Any, installed: 1.13.0]
Flask-Session==0.3.1
Flask-SocketIO==4.2.1
- Flask [required: >=0.9, installed: 1.1.1]
- python-socketio [required: >=4.3.0, installed: 4.5.1]
- python-engineio [required: >=3.9.0, installed: 3.12.1]
- six [required: >=1.9.0, installed: 1.13.0]
- six [required: >=1.9.0, installed: 1.13.0]
google==2.0.1
- beautifulsoup4 [required: Any, installed: 4.8.0]
html2text==2019.8.11
impacket==0.9.20
- ldap3 [required: ==2.5.1, installed: ?]
- ldapdomaindump [required: >=0.9.0, installed: ?]
ipython==5.8.0
- backports.shutil-get-terminal-size [required: Any, installed: 1.0.0]
- pathlib2 [required: Any, installed: 2.3.5]
- scandir [required: Any, installed: 1.10.0]
- pexpect [required: Any, installed: 4.6.0]
pip 还没有真正的依赖解析。警告将打印到 stderr(标准错误)而不是 stdout(标准输出)。要完全消除此警告,请使用-w静音或–warn静音标志。也可以使用–warn fail将其设置为严格模式,在这种情况下,该命令不仅会将警告打印到 stderr,还会以非零状态码退出。如果您想将此工具安装到 CI 管道中,这可能会很有用。
注意: -warn 标志是在 0.6.0 版中添加的。对于旧版本,使用–nowarn 标志。
2. 循环依赖:当两个包相互依赖时,就会出现这种依赖。假设包 A 依赖包 B,包 B 依赖包 A。
为此,让我们再看一个例子:
命令:
$pipdeptree
输出:
Warning!!! Cyclic dependencies found:
- CircularDependencyA => CircularDependencyB => CircularDependencyA
- CircularDependencyB => CircularDependencyA => CircularDependencyB
------------------------------------------------------------------------
wsgiref==0.1.2
argparse==1.2.1
注意:它们也打印到 stderr 并且可以使用–warn 标志进行控制。
要查找安装特定软件包的原因:
现在,我们有时可能想知道为什么要安装一个特定的包。然后我们可以为此使用-reverse(或简单的-r)标志。要找出所有包需要特定包的内容,可以将其与–packages结合使用
标志如下例所示:
命令:
$pipdeptree --reverse --packages MarkupSafe,numpy
输出:
MarkupSafe==1.1.1
- Jinja2==2.11.2 [requires: MarkupSafe>=0.23]
- altair==4.1.0 [requires: jinja2]
- Mako==1.1.3 [requires: MarkupSafe>=0.9.2]
numpy==1.18.4
- altair==4.1.0 [requires: numpy]
- opencv-python==4.2.0.34 [requires: numpy>=1.17.3]
- pandas==1.0.4 [requires: numpy>=1.13.3]
- altair==4.1.0 [requires: pandas>=0.18]
- scipy==1.5.2 [requires: numpy>=1.14.5]
使用 pipdeptree 编写 requirements.txt 文件:
如果您希望仅跟踪requirements.txt文件中的顶级包,可以使用 pipdeptree 通过 grep-ing 仅输出中的顶级行来实现,
命令:
$pipdeptree | grep -P '^\w+'
输出:
Lookupy==0.1
wsgiref==0.1.2
argparse==1.2.1
psycopg2==2.5.2
Flask-Script==0.6.6
alembic==0.6.2
ipython==2.0.0
slugify==0.0.1
redis==2.9.1
不过这里有一个问题。输出没有提到任何关于Lookupy的内容 被安装为一个可编辑的包(参考上面 pip freeze 的输出)并且关于它的源的信息丢失了。要解决此问题,必须使用-f或–freeze 标志运行 pipdeptree
命令:
$pipdeptree -f --warn silence | grep -P '^[\w0-9\-=.]+'
输出:
-e git+git@github.com:naiquevin/lookupy.git@cdbe30c160e1c29802df75e145ea4ad903c05386#egg=Lookupy-master
wsgiref==0.1.2
argparse==1.2.1
psycopg2==2.5.2
Flask-Script==0.6.6
alembic==0.6.2
ipython==2.0.0
slugify==0.0.1
redis==2.9.1
命令:
$ pipdeptree -f --warn silence | grep -P '^[\w0-9\-=.]+' > requirements.txt
freeze 标志也不会输出子依赖项的连字符,因此您可以将pipdeptree -f的完整输出转储到requirements.txt文件,使文件对人类友好(由于缩进)以及 pip 友好。 (不过要注意重复的依赖项)
将 pipdeptree 与外部工具一起使用:
pipdeptree 使用标志 –json 以 JSON 表示形式显示输出,如下所示:
命令:
$pipdeptree --json
输出:
[
{
"package": {
"key": "werkzeug",
"package_name": "Werkzeug",
"installed_version": "1.0.1"
},
"dependencies": []
},
{
"package": {
"key": "urllib3",
"package_name": "urllib3",
"installed_version": "1.25.9"
},
"dependencies": []
},
{
"package": {
"key": "pytz",
"package_name": "pytz",
"installed_version": "2020.1"
},
"dependencies": []
},
{
"package": {
"key": "python-dateutil",
"package_name": "python-dateutil",
"installed_version": "2.8.1"
},
"dependencies": [
{
"key": "six",
"package_name": "six",
"installed_version": "1.15.0",
"required_version": ">=1.5"
}
]
},
{
"package": {
"key": "pyrsistent",
"package_name": "pyrsistent",
"installed_version": "0.16.0"
},
"dependencies": [
{
"key": "six",
"package_name": "six",
"installed_version": "1.15.0",
"required_version": null
}
]
},
{
"package": {
"key": "pipdeptree",
"package_name": "pipdeptree",
"installed_version": "1.0.0"
},
"dependencies": [
{
"key": "pip",
"package_name": "pip",
"installed_version": "20.2.1",
"required_version": ">=6.0.0"
}
]
},
]
注意: –json将输出所有包及其直接依赖项的平面列表。要获取嵌套的 JSON,请使用--json-tree (在 0.11.0 版本中添加)。
命令:
$pipdeptree --json-tree
输出:
[
{
"key": "altair",
"package_name": "altair",
"installed_version": "4.1.0",
"required_version": "4.1.0",
"dependencies": [
{
"key": "entrypoints",
"package_name": "entrypoints",
"installed_version": "0.3",
"required_version": "Any",
"dependencies": []
},
{
"key": "jinja2",
"package_name": "jinja2",
"installed_version": "2.11.2",
"required_version": "Any",
"dependencies": [
{
"key": "markupsafe",
"package_name": "MarkupSafe",
"installed_version": "1.1.1",
"required_version": ">=0.23",
"dependencies": []
}
]
},
{
"key": "urllib3",
"package_name": "urllib3",
"installed_version": "1.25.9",
"required_version": "1.25.9",
"dependencies": []
}
]