Python:以编程方式运行“pip list”。

32

我正在编写一段代码,用于报告和协调两个由pip管理的Python安装之间的差异。

如何在不调用pip子程序的情况下以编程方式获取pip list提供的信息?


如果一个库在内部使用subprocess,这会有问题吗? - Cripto
只要我的代码不需要了解在我不熟悉的各种系统上调用子进程的复杂性,那就没问题。 - Mark Harrison
OP。在看到@vittore的答案之前,我已经写了那个问题。我喜欢那个答案。 - Cripto
使用可以随时销毁和重建的虚拟环境不是一个选项吗?这是标准的做法。 - jpmc26
@jpmc,不确定,它们不是我的安装...但我会转达你的留言! - Mark Harrison
6个回答

37

截至2019年2月1日的最佳答案已经过时,无法与更新的pip版本一起使用。

但是不用担心 - 仍然可以通过编程方式获取软件包列表:

选项:

A. _internal.main

from pip import _internal
_internal.main(['list'])

这将打印出三列,分别是“Package”、“Version”和“Location”。

请注意,不推荐使用pip的内部api。

B. pkg_resources

import pkg_resources
print([p.project_name for p in pkg_resources.working_set])
# note that this is same as calling pip._vendor.pkg_resources.working_set

C. iter_modules

执行时间较长(在配备I5 CPU,SSD和8GB内存的计算机上约为300毫秒)。

好处是它将具有更广泛的模块列表,并输出可导入的名称。

例如:python-dateutil 作为 dateutil 被导入,但 iter_modules 将给出导入名称:dateutil。

from pkgutil import iter_modules
print([p.name for p in iter_modules()])

D. 通过subprocess在命令行中调用pip

这个问题的解决方案很简单,我会把它留给读者作为练习。

也就是说我懒得做这个,祝你好运!:D


1
我已验证了A和B,并接受了这个答案。非常棒! - Mark Harrison
1
如果您还想知道软件包的版本,那么使用 pkg_resources 是最好的方法,因为返回的对象有一个 .version 成员变量来包含它。 - Konstantin

19

Python 3.6和Pip 19.0.1的更新

> from pip._internal.utils.misc import get_installed_distributions
> p = get_installed_distributions()
> pprint.pprint(p)

[wheel 0.32.3 (/usr/local/lib/python3.7/site-packages),
 wcwidth 0.1.7 (/usr/local/lib/python3.7/site-packages),
 virtualenv 16.0.0 (/usr/local/lib/python3.7/site-packages),
 virtualenv-clone 0.3.0 (/usr/local/lib/python3.7/site-packages),
 urllib3 1.24.1 (/usr/local/lib/python3.7/site-packages),
 typing 3.6.6 (/usr/local/lib/python3.7/site-packages),
 terminaltables 3.1.0 (/usr/local/lib/python3.7/site-packages),
 ...

原始答案

Pip只是Python模块,所以只需导入它并调用list

import pip

pip.main(['list'])

# you can get details on package using show:

pip.main(['show', 'wheel'])

嗯,所以有更好的方法:
pip.utils.get_installed_distributions()

返回已安装软件包列表。

packages = pip.utils.get_installed_distributions()

p = packages[0]

p.project_name 
p.version
p.egg_name
p.location

您可以从源代码这里了解pip list的操作。

get_installed_distributions还接受许多参数,以仅返回本地软件包(来自当前虚拟环境)等。请参见此处的帮助文档。

还有来自_vendor模块的底层命令:

list(pip._vendor.pkg_resources.working_set)

然而,get_installed_distributions 提供了更简单的 API。


这里提供了多种方法:https://dev59.com/qG435IYBdhLWcg3w6EqF,但我不喜欢它们中的任何一种,因为它们都很繁琐。让我看看如何以稍微不同的方式调用它。 - vittore
@vittore 我找不到pip模块的文档。我一直在找 这个链接。但是那里没有关于模块API的任何内容。 - Cripto
太好了,谢谢!了解pip.main()也很有趣。 - Mark Harrison
1
似乎pip改变了其结构。从pip 19.0.1开始(在Python 3.6上,尽管我认为这并不重要),这个命令可以正常工作:from pip._internal.utils.misc import get_installed_distributions; p = get_installed_distributions() - Joao Coelho
1
非常感谢,看起来确实是这样(他们甚至在相关测试中使用它),但这个变化让我感到困惑。同时,我使用了setuptools中的pkg_resources收集所有许可证 - raratiru
显示剩余9条评论

3

python -m pip list (robust method)

import subprocess
import sys

def pip_list():
    args = [sys.executable, "-m", "pip", "list"]
    p = subprocess.run(args, check=True, capture_output=True)
    return p.stdout.decode()

print(pip_list())

@Aaron提到

从脚本安装软件包的官方推荐方法是通过调用pip的命令行界面来实现子进程。这里介绍的大多数其他答案都不受pip支持。此外,自pip v10以来,所有代码都已移动到pip._internal,以明确向用户表示禁止对pip进行编程使用。

使用sys.executable确保您将调用与当前运行时相关联的相同pip


3
使用os模块或system模块。
import os 
import subprocess as su
os.system("pip list")
su.call(["pip","list"])

2
最有用的答案。但是这两个调用的输出都是int类型,为什么呢? - Vaidøtas I.
这也适用于返回conda list,但存在相同的整数问题。 - MinneapolisCoder9
@VaidøtasI。@MinnegapolisCoder9使用pip_list = os.popen('pip list').read()将调用的输出分配给变量。(对于conda list也是如此)在Unix上,os.system的返回值是调用的退出状态。 - Ken Jiiii

0

在这里测试了一些解决方案,发现要么非常缓慢,要么已经弃用,要么在我的Python 3.10上返回错误,因此我使用以下解决方案:

注意

此函数可以检索所有已安装的包及其已安装版本。

import pkg_resources

def get_installed_packages():
    installed_packages = []
    for package in pkg_resources.working_set:
        installed_packages.append(package.key)
    return installed_packages

def get_package_version(package_name):
    try:
        return pkg_resources.get_distribution(package_name).version
    except pkg_resources.DistributionNotFound:
        return None

# Get a list of all installed packages
installed_packages = get_installed_packages()

# Iterate over the installed packages and get their versions
package_versions = {}
for package_name in installed_packages:
    version = get_package_version(package_name)
    package_versions[package_name] = version

# Print the package versions
for package_name, version in package_versions.items():
    print(f"{package_name} - {version}")

示例输出:

xlsxwriter - 3.0.9
argcomplete - 2.0.0
comm - 0.1.2
debugpy - 1.6.6

结合正则表达式列出软件包名称,我们可以检查所有已安装版本是否实际安装在计算机上。

import re

def extract_package_names(file_path):
    with open(file_path, 'r') as file:
        requirements = file.readlines()

    package_names = []
    for requirement in requirements:
        match = re.search(r'^([\w.-]+)', requirement)
        if match:
            package_names.append(match.group(1))

    return package_names

# Example usage
file_path = 'requirements.txt'
package_names = extract_package_names(file_path)
print(package_names)

示例输出:

['numpy', 'pandas', 'xlsxwriter']

两者结合:

file_path = 'requirements.txt'
package_names = extract_package_names(file_path)

packages = {}
for package_name in package_names:
    packages[package_name] = get_package_version(package_name)
packages

注意
免责声明:本代码是在ChatGPT 3.5的帮助下编写的。

-1
为了完整起见,这里是vittore的pip.main()想法,通过捕获stdout进行了详细说明。当然,使用get_installed_distributions()是首选解决方案。
import contextlib
@contextlib.contextmanager
def capture():
    import sys
    from cStringIO import StringIO
    oldout,olderr = sys.stdout, sys.stderr
    try:
        out=[StringIO(), StringIO()]
        sys.stdout,sys.stderr = out
        yield out
    finally:
        sys.stdout,sys.stderr = oldout, olderr
        out[0] = out[0].getvalue()
        out[1] = out[1].getvalue()

with capture() as out:
    import pip
    pip.main(['list'])

print out
    ['awscli (1.7.45)\nboto (2.38.0) ...

pip不再支持main了 - 请参见https://dev59.com/-1UL5IYBdhLWcg3wv6Vx - Almenon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接