pip 批量完全卸载包
-
- 创作背景
- 问题分析
- 解决方法
- 代码详解
- 改 BUG
- 结尾
创作背景
因为我本机环境中安装的第三方库太多了,所以今天我准备把它们都卸载了,但因为太多了,所以不可能手动一个一个来,于是我便写了个小脚本,本文就记录这个脚本的作用及使用。
如果觉得我这篇文章写的好的话,能不能给我 点个赞
,评论
、收藏
一条龙(☆▽☆)。如果要点个 关注
的话也不是不可以🤗。
请各位参加一下文末的 投票
哦,如果 有什么不足之处,还 请各位大佬在评论区提出,不胜感激。
问题分析
要实现上述功能,我们需要解决以下问题:
- 获取所有已安装的包
- 获取每个包的依赖
- 命令行交互,卸载指定包
解决方法
上述三个问题均可以使用 subprocess.Popen 包进行解决。为了方便,第三问题使用 subprocess.run 解决。 网上已经有好多文章都对 subprocess.Popen 和 subprocess.run 的参数进行解释,这里不多赘述。 对于 subprocess.Popen ,除了要执行的命令外,我只设置了 stdin、stdout、stderr 参数。 对于subprocess.run ,除了要执行的命令外,我只设置了如下参数:
- universal_newlines ,设置输入输出的数据类型,True 为字符串,否则为字节串。
- capture_output ,设置是否显示命令执行结果,True 显示,否则不显示。
- input ,这个是关键,使得代码可以与命令行进行交互,即指定命令后,在命令行输入内容执行。在本文中的作用是执行 pip uninstall 【包名】 后输入 y 进行确定。
代码详解
首先导入所需的库:re 、subprocess 。 然后将卸载一个包的代码封装成一个函数,如下(本菜鸡代码水平不足,还请各位大佬指出问题):
def uninstall_completely(name):
# 必备的或不需要卸载的库,可以自行设置
skips = ['pip', 'urllib3', 'setuptools', 'wheel']
if name in skips or name.startswith('-'):
return
print(f'Start to uninstall {
name}')
# 初始化 Popen,读取命令 pip show 【包名】 的执行结果
pipe = subprocess.Popen(f'pip show {
name}', stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# 如果没有安装该包,则退出
if b'WARNING: Package(s) not found: ' in pipe.stderr.read():
print(f'An error occurred when uninstalling {
name}: {
name} 不存在\n')
return
# 正则匹配获得所有依赖包名
# stdout.read() 的结果是字节串,需要转换为字符串
requirements = ''.join(re.findall('Requires: (.*?)\r\n', pipe.stdout.read().decode()))
print(f"{
name}'s requirements: {
requirements}")
# 关闭命令行
pipe.terminate()
# 卸载指定包
try:
# 执行命令 pip uninstall 【包名】
# 执行命令后需要输入是否卸载 [y/n],因为要卸载,所以指定 input 参数为 'y'
obj = subprocess.run(f'pip uninstall {
name}', universal_newlines=True, capture_output=True, input='y')
# 如果出错,则输出报错原因
if not obj.stderr == '':
print(obj.stderr)
return
# 否则卸载成功
else:
print(f'Uninstall {
name} successfully.')
# 防止中途报错导致程序停止运行
except Exception as e:
print(f'An error occurred when uninstalling {
name}: {
e}')
# 输出结果分隔
print('-------------------------------------------')
# 卸载指定包的所有依赖包,递归调用本函数
for _ in requirements.split(', '):
if r == '':
continue
uninstall_completely(_)
调用函数代码如下:
for line in subprocess.Popen('pip list', stdout=subprocess.PIPE).stdout.read().decode().split('\n')[2:]:
name = line.split(' ')[0]
if name == '':
continue
uninstall_completely(name)
其中:
- pip list 可以查看当前安装的所有包。
- .decode() 是因为 stdout.read() 的结果是字节串,需要将其转为字符串。
- [2:] 去除如下图所示的无用行
如果只卸载单个包的话,直接调用函数。 如果卸载部分包的话,遍历列表并分别调用函数。
改 BUG
写代码的时候 BUG 并不少见,但这次挺少的。出错的原因是读取执行结果时编码错误导致。 具体过程为 run 函数中调用 Popen.communicate() 函数,如下:
with Popen(*popenargs, **kwargs) as process:
try:
stdout, stderr = process.communicate(input, timeout=timeout)
except TimeoutExpired as exc:
process.kill()
然后调用 Popen._communicate() 函数,如下:
try:
stdout, stderr = self._communicate(input, endtime, timeout)
except KeyboardInterrupt:
...
又调用 Popen._readerthread() 函数,如下:
self.stdout_thread = threading.Thread(target=self._readerthread,
args=(self.stdout, self._stdout_buff))
看一下 Popen._readerthread() ,如下:
def _readerthread(self, fh, buffer):
buffer.append(fh.read())
fh.close()
此时会从 Popen.stdout 中读取命令执行结果。
再看一下 Popen.stdout 的初始化代码,如下:
self.text_mode = encoding or errors or text or universal_newlines
...
self.stdout = io.open(c2pread, 'rb', bufsize)
if self.text_mode:
self.stdout = io.TextIOWrapper(self.stdout, encoding=encoding, errors=errors)
此时就明了了,如果指定了 encoding 、errors、text、universal_newlines 中任意一个或多个参数,就意味着输出的结果是 字符串 ,而如果没有指定 encoding 参数的话,默认是使用 gbk 编码,如果和环境中的编码方式不一致的话会导致编码报错。
那我们可以修改一下 Popen 的源码,在 subprocess 中第 767 行 self.text_mode 的定义下一行加入如下代码:
if self.text_mode and encoding is None:
encoding = sys.getdefaultencoding()
如果要将字节串转为字符串并且没有指定编码格式的话,就使用环境默认编码。
结尾
有想要一起学习 python
的小伙伴可以 私信我
进群哦。
以上就是我要分享的内容,因为 学识尚浅,会有不足,还 请各位大佬指正。 有什么问题也可在评论区留言。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/181096.html原文链接:https://javaforall.cn