Python之pip图形化(GUI界面)辅助管理工具
Python之pip图形化(GUI界面)辅助管理工具
pip 是 Python 的包管理工具,用于安装、管理、更新和卸载 Python 包(模块)。用于第三方库的安装和管理过程,是 Python 开发中不可或缺的工具。
包的安装、更新、卸载,查看,特别是当用户安装有多个版本的Python时,为特定版本进行这些操作。还可以使用镜像加速安装。
默认情况下,pip 会从 PyPI 下载包。如果你在中国大陆,可能会因为网络问题导致下载速度较慢。可以设置使用国内的镜像源。
有关详情可见:https://blog.csdn.net/cnds123/article/details/104393385
对于新手而言,还是比较麻烦的。
为此,提供一个pip图形化(GUI界面)辅助管理工具
首先搜索出电脑上安装的python版本及路径放在列表中,用户点击哪个就列出哪个版本中已安装的包。所有耗时操作添加了操作前的提示信息,并在操作结束后显示明确的结果。
“列出python”按钮,提供计算机中安装所有python版本(含路径)
“安装包”按钮,提供对话框输入包名进行安装
“包卸载”按钮,可卸载选中的Python包
“包升级”按钮,可升级选中的Python包到最新版本
“镜像源”按钮,出现内置清华、阿里云、腾讯云和官方源等多个镜像源,用于选择
“刷新”按钮,用于列表中显示刚刚安装的包
运行效果如下:
这个工具,利用了多个模块/包/库:
sys(提供与 Python 解释器强相关的功能)、os(提供操作系统相关功能)、subprocess(用于运行外部命令或程序)、json(用于处理 JSON 数据)、shutil(提供文件和文件集合的高级操作)、datetime (用于处理日期和时间),这些都
是 Python 的标准库,不需要安装。
PyQt6 是一个功能强大的 GUI 框架,适用于开发复杂的桌面应用程序。通过安装 PyQt6,你可以使用上述导入的模块来构建用户界面。
源码如下:
import sys
import os
import subprocess
import json
import shutil
from datetime import datetime# PyQt6模块导入
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QPushButton, QVBoxLayout, QHBoxLayout,QListWidget, QListWidgetItem, QLabel, QDialog, QFormLayout, QLineEdit,QDialogButtonBox, QMessageBox, QTextEdit, QRadioButton, QButtonGroup,QStatusBar, QSplitter
)
from PyQt6.QtCore import Qt, QSize
from PyQt6.QtGui import QColorclass PipManager(QMainWindow):"""PIP图形化管理工具主窗口类"""def __init__(self):super().__init__()# 窗口基本设置self.setWindowTitle('Python PIP图形化管理工具 — V 1.0.1')self.setGeometry(100, 100, 800, 600)# 初始化成员变量self.python_installations = [] # 存储Python安装信息self.current_python = None # 当前选中的Pythonself.installed_packages = [] # 当前Python已安装的包self.current_mirror = None # 当前使用的镜像源# 初始化镜像源配置self.mirrors = {"清华": "https://pypi.tuna.tsinghua.edu.cn/simple","阿里云": "https://mirrors.aliyun.com/pypi/simple","腾讯云": "https://mirrors.cloud.tencent.com/pypi/simple","官方源": "https://pypi.org/simple"}# 初始化UIself.init_ui()# 加载配置self.load_config()# 显示欢迎信息self.info_text.setText("欢迎使用Python PIP图形化管理工具\n\n""请点击「列出Python」按钮扫描系统中的Python安装\n""或者直接选择上方列表中的Python版本开始管理")# 加载Python安装列表self.find_python_installations()def init_ui(self):"""初始化用户界面组件"""# 创建主窗口部件main_widget = QWidget()self.setCentralWidget(main_widget)# 主布局main_layout = QVBoxLayout()main_widget.setLayout(main_layout)# 上方部分 - Python版本列表python_group = QWidget()python_layout = QVBoxLayout()python_group.setLayout(python_layout)python_label = QLabel('已安装的Python列表')python_layout.addWidget(python_label)self.python_list = QListWidget()self.python_list.itemClicked.connect(self.on_python_selected)python_layout.addWidget(self.python_list)main_layout.addWidget(python_group)# 按钮工具栏button_layout = QHBoxLayout()self.list_python_btn = QPushButton('列出Python')self.list_python_btn.clicked.connect(self.find_python_installations)button_layout.addWidget(self.list_python_btn)self.install_pkg_btn = QPushButton('安装包')self.install_pkg_btn.clicked.connect(self.install_package)button_layout.addWidget(self.install_pkg_btn)self.uninstall_pkg_btn = QPushButton('卸载包')self.uninstall_pkg_btn.clicked.connect(self.uninstall_package)button_layout.addWidget(self.uninstall_pkg_btn)self.upgrade_pkg_btn = QPushButton('升级包')self.upgrade_pkg_btn.clicked.connect(self.upgrade_package)button_layout.addWidget(self.upgrade_pkg_btn)self.mirror_btn = QPushButton('设置镜像源')self.mirror_btn.clicked.connect(self.show_mirror_dialog)button_layout.addWidget(self.mirror_btn)self.refresh_btn = QPushButton('刷新')self.refresh_btn.clicked.connect(self.refresh_package_list)button_layout.addWidget(self.refresh_btn)main_layout.addLayout(button_layout)# 下方分割区域:包列表和信息显示splitter = QSplitter(Qt.Orientation.Horizontal)# 左侧 - 已安装包列表package_group = QWidget()package_layout = QVBoxLayout()package_group.setLayout(package_layout)package_label = QLabel('已安装的包列表')package_layout.addWidget(package_label)self.package_list = QListWidget()self.package_list.itemClicked.connect(self.show_package_info)package_layout.addWidget(self.package_list)splitter.addWidget(package_group)# 右侧 - 信息日志info_group = QWidget()info_layout = QVBoxLayout()info_group.setLayout(info_layout)info_label = QLabel('信息日志')info_layout.addWidget(info_label)self.info_text = QTextEdit()self.info_text.setReadOnly(True)info_layout.addWidget(self.info_text)splitter.addWidget(info_group)# 设置分割比例splitter.setSizes([300, 300])main_layout.addWidget(splitter)# 状态栏self.status_bar = QStatusBar()self.setStatusBar(self.status_bar)# 显示当前镜像源self.mirror_label = QLabel('镜像源: 未设置')self.status_bar.addPermanentWidget(self.mirror_label)# 显示当前Python版本self.python_label = QLabel('Python: 未选择')self.status_bar.addPermanentWidget(self.python_label)# 初始状态self.status_bar.showMessage('准备就绪', 3000)def load_config(self):"""加载配置文件"""config_path = os.path.expanduser('~/.pip-multi-gui-config')try:# 尝试加载配置文件if os.path.exists(config_path):with open(config_path, 'r', encoding='utf-8') as f:config = json.load(f)self.current_mirror = config.get('current_mirror')if self.current_mirror:self.mirror_label.setText(f'镜像源: {self.get_mirror_name(self.current_mirror)}')except Exception as e:QMessageBox.warning(self, '配置错误', f'加载配置文件失败: {str(e)}')def save_config(self):"""保存配置文件"""config_path = os.path.expanduser('~/.pip-multi-gui-config')config = {'current_mirror': self.current_mirror}try:with open(config_path, 'w', encoding='utf-8') as f:json.dump(config, f, ensure_ascii=False, indent=4)except Exception as e:QMessageBox.warning(self, '配置错误', f'保存配置文件失败: {str(e)}')def get_mirror_name(self, url):"""根据URL获取镜像源名称"""for name, mirror_url in self.mirrors.items():if mirror_url == url:return namereturn '自定义源'def _get_startupinfo(self):"""获取隐藏命令窗口的startupinfo对象"""if sys.platform.startswith('win'):startupinfo = subprocess.STARTUPINFO()startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOWstartupinfo.wShowWindow = subprocess.SW_HIDEreturn startupinforeturn Nonedef find_python_installations(self):"""查找系统中安装的所有Python版本"""self.python_installations = []self.python_list.clear()# 添加日志提示self.info_text.clear()self.info_text.append("正在扫描系统中的Python安装,请稍候...\n")QApplication.processEvents() # 立即更新UItry:# 在Windows系统上查找Python安装if sys.platform == 'win32':self._find_windows_pythons()# 在Unix/Linux/Mac系统上查找Python安装else:self._find_unix_pythons()# 更新UIfor python in self.python_installations:item = QListWidgetItem(f"{python['version']} ({python['path']})")self.python_list.addItem(item)# 添加结果日志self.info_text.append(f"扫描完成,找到 {len(self.python_installations)} 个Python安装\n")for i, python in enumerate(self.python_installations, 1):self.info_text.append(f"{i}. {python['version']} - {python['path']}")# 如果有Python安装,选择第一个if self.python_installations:self.python_list.setCurrentRow(0)self.on_python_selected(self.python_list.item(0))self.status_bar.showMessage(f'找到 {len(self.python_installations)} 个Python安装', 3000)except Exception as e:self.info_text.append(f"扫描失败: {str(e)}")QMessageBox.critical(self, '错误', f'查找Python安装失败: {str(e)}')def _find_windows_pythons(self):"""查找Windows上的Python安装"""# 可能的Python安装路径possible_paths = []# 检查Program Filesprogram_files = [os.environ.get('ProgramFiles', 'C:\\Program Files'),os.environ.get('ProgramFiles(x86)', 'C:\\Program Files (x86)')]# 添加用户目录下的AppData\Localuser_profile = os.environ.get('USERPROFILE', '')if user_profile:program_files.append(os.path.join(user_profile, 'AppData', 'Local'))# 查找Python目录for base_dir in program_files:if os.path.exists(base_dir):for item in os.listdir(base_dir):if item.lower().startswith('python'):python_dir = os.path.join(base_dir, item)possible_paths.append(python_dir)# 添加系统PATH中的Pythonfor path in os.environ.get('PATH', '').split(os.pathsep):if 'python' in path.lower():possible_paths.append(path)# 检查每个可能的路径for path in possible_paths:python_exe = os.path.join(path, 'python.exe')if os.path.exists(python_exe):self._add_python_installation(python_exe)def _find_unix_pythons(self):"""查找Unix/Linux/Mac上的Python安装"""# 常见的Python可执行文件名python_executables = ['python', 'python3', 'python2']# 使用which命令查找Pythonfor exe in python_executables:try:result = subprocess.run(['which', exe], capture_output=True, text=True, check=False)if result.returncode == 0 and result.stdout.strip():self._add_python_installation(result.stdout.strip())except Exception:pass# 检查常见安装位置common_paths = ['/usr/bin', '/usr/local/bin', '/opt/local/bin','/Library/Frameworks/Python.framework/Versions']for base_path in common_paths:if os.path.exists(base_path):for item in os.listdir(base_path):if any(item.startswith(exe) for exe in python_executables):python_path = os.path.join(base_path, item)if os.path.isfile(python_path) and os.access(python_path, os.X_OK):self._add_python_installation(python_path)def _add_python_installation(self, python_path):"""添加Python安装到列表"""try:# 获取Python版本result = subprocess.run([python_path, '--version'],capture_output=True, text=True, check=False,startupinfo=self._get_startupinfo())if result.returncode == 0:version = result.stdout.strip() or result.stderr.strip()# 检查是否已存在相同路径for existing in self.python_installations:if existing['path'] == python_path:returnself.python_installations.append({'path': python_path,'version': version})except Exception:passdef on_python_selected(self, item):"""处理Python选择事件"""if not item:returnindex = self.python_list.row(item)if 0 <= index < len(self.python_installations):self.current_python = self.python_installations[index]self.python_label.setText(f"Python: {self.current_python['version']}")self.refresh_package_list()def refresh_package_list(self):"""刷新当前Python的已安装包列表"""if not self.current_python:QMessageBox.warning(self, '警告', '请先选择一个Python版本')return# 添加日志提示self.info_text.clear()self.info_text.append(f"正在获取 {self.current_python['version']} 的已安装包列表...\n")QApplication.processEvents() # 立即更新UItry:# 执行pip list命令获取已安装包result = subprocess.run([self.current_python['path'], '-m', 'pip', 'list', '--format=json'],capture_output=True, text=True, check=False,startupinfo=self._get_startupinfo())if result.returncode == 0:# 解析JSON结果self.installed_packages = json.loads(result.stdout)self.update_package_list_display()# 更新日志self.info_text.append(f"成功获取 {len(self.installed_packages)} 个已安装的包\n")self.info_text.append("选择左侧列表中的包可查看详细信息")self.status_bar.showMessage('包列表刷新成功', 3000)else:error_msg = result.stderr.strip() or '未知错误'raise Exception(error_msg)except Exception as e:self.info_text.append(f"获取包列表失败: {str(e)}")QMessageBox.critical(self, '错误', f'获取包列表失败: {str(e)}')self.status_bar.showMessage('获取包列表失败', 3000)def update_package_list_display(self):"""更新包列表显示"""self.package_list.clear()# 按名称排序sorted_packages = sorted(self.installed_packages, key=lambda x: x['name'].lower())# 添加到列表控件for pkg in sorted_packages:item = QListWidgetItem(f"{pkg['name']}=={pkg['version']}")self.package_list.addItem(item)def show_package_info(self, item):"""显示选中包的详细信息"""if not item:returnpkg_name = item.text().split('==')[0]# 添加日志提示self.info_text.clear()self.info_text.append(f"正在获取 {pkg_name} 的详细信息...\n")QApplication.processEvents() # 立即更新UItry:# 执行pip show命令获取包详情result = subprocess.run([self.current_python['path'], '-m', 'pip', 'show', pkg_name],capture_output=True, text=True, check=False,startupinfo=self._get_startupinfo())if result.returncode == 0:self.info_text.clear()self.info_text.append(f"== {pkg_name} 详细信息 ==\n")self.info_text.append(result.stdout)else:raise Exception(result.stderr.strip() or '未知错误')except Exception as e:self.info_text.append(f"获取包信息失败: {str(e)}")def install_package(self):"""安装Python包"""if not self.current_python:QMessageBox.warning(self, '警告', '请先选择一个Python版本')return# 创建自定义对话框dialog = QDialog(self)dialog.setWindowTitle('安装包')layout = QFormLayout()# 包名输入框pkg_name_edit = QLineEdit()layout.addRow('包名:', pkg_name_edit)# 版本号输入框version_edit = QLineEdit()version_edit.setPlaceholderText('可选,如: 1.0.0')layout.addRow('版本号:', version_edit)# 确定和取消按钮btn_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)btn_box.accepted.connect(dialog.accept)btn_box.rejected.connect(dialog.reject)layout.addRow(btn_box)dialog.setLayout(layout)if dialog.exec() == QDialog.DialogCode.Accepted:pkg_name = pkg_name_edit.text().strip()version = version_edit.text().strip()if not pkg_name:QMessageBox.warning(self, '警告', '包名不能为空')return# 显示使用的镜像源信息mirror_info = ""if self.current_mirror:mirror_info = f"\n镜像源: {self.get_mirror_name(self.current_mirror)}"# 确认安装confirm = QMessageBox.question(self, '确认安装',f'确定要安装 {pkg_name}{"=="+version if version else ""} 吗?{mirror_info}',QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)if confirm == QMessageBox.StandardButton.Yes:self.run_pip_command('install', pkg_name, version=version)def uninstall_package(self):"""卸载Python包"""if not self.current_python:QMessageBox.warning(self, '警告', '请先选择一个Python版本')returnselected_item = self.package_list.currentItem()if not selected_item:QMessageBox.warning(self, '警告', '请先选择一个包')returnpkg_name = selected_item.text().split('==')[0]# 确认卸载confirm = QMessageBox.question(self, '确认卸载',f'确定要卸载 {pkg_name} 吗?',QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)if confirm == QMessageBox.StandardButton.Yes:self.run_pip_command('uninstall', pkg_name)def upgrade_package(self):"""升级Python包"""if not self.current_python:QMessageBox.warning(self, '警告', '请先选择一个Python版本')returnselected_item = self.package_list.currentItem()if not selected_item:QMessageBox.warning(self, '警告', '请先选择一个包')returnpkg_name = selected_item.text().split('==')[0]# 确认升级confirm = QMessageBox.question(self, '确认升级',f'确定要升级 {pkg_name} 吗?',QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)if confirm == QMessageBox.StandardButton.Yes:self.run_pip_command('install', pkg_name, upgrade=True)def run_pip_command(self, command, pkg_name, version=None, upgrade=False):"""执行pip命令的通用方法"""if not self.current_python:QMessageBox.warning(self, '警告', '请先选择一个Python版本')return# 构建命令cmd = [self.current_python['path'], '-m', 'pip', command, pkg_name]# 卸载时自动确认if command == 'uninstall':cmd.append('--yes')# 添加版本号if version:cmd[-1] = f"{pkg_name}=={version}"# 添加升级标志if upgrade:cmd.append('--upgrade')# 添加镜像源(卸载操作不需要)if self.current_mirror and command != 'uninstall':cmd.extend(['-i', self.current_mirror])host = self.current_mirror.split('//')[1].split('/')[0]cmd.extend(['--trusted-host', host])# 清空并准备显示输出self.info_text.clear()self.info_text.append(f"执行命令: {' '.join(cmd)}\n")self.info_text.append("正在处理,请稍候...\n")QApplication.processEvents() # 立即更新UItry:# 创建隐藏窗口的startupinfo对象(仅Windows系统)startupinfo = self._get_startupinfo()# 执行命令并实时输出process = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE,text=True,bufsize=1,universal_newlines=True,startupinfo=startupinfo # 添加这个参数来隐藏窗口)# 实时读取输出while True:output = process.stdout.readline()if output == '' and process.poll() is not None:breakif output:self.info_text.append(output.strip())QApplication.processEvents() # 更新UI# 获取最终返回码return_code = process.wait()# 显示结果if return_code == 0:operation_name = {'install': '安装','uninstall': '卸载',}op_name = operation_name.get(command, command)if upgrade:op_name = '升级'success_msg = f"\n{op_name}操作成功完成!"self.info_text.append(success_msg)self.status_bar.showMessage(f"{op_name} {pkg_name} 成功", 5000)self.refresh_package_list()else:error_output = process.stderr.read()self.info_text.append(f"\n错误信息:\n{error_output}")raise Exception(error_output)except Exception as e:self.status_bar.showMessage(f"操作失败", 5000)QMessageBox.critical(self, '操作失败',f'操作执行失败:\n{str(e)}')self.info_text.append(f"\n错误信息:\n{str(e)}")def show_mirror_dialog(self):"""显示镜像源设置对话框"""# 创建对话框dialog = QDialog(self)dialog.setWindowTitle('设置镜像源')layout = QVBoxLayout()# 添加说明layout.addWidget(QLabel("选择或输入要使用的PyPI镜像源:"))# 添加镜像源选项mirror_group = QButtonGroup()for i, (name, url) in enumerate(self.mirrors.items()):radio = QRadioButton(f"{name} ({url})")radio.setProperty('url', url)if url == self.current_mirror:radio.setChecked(True)mirror_group.addButton(radio, i)layout.addWidget(radio)# 自定义镜像源输入custom_radio = QRadioButton('自定义')mirror_group.addButton(custom_radio, len(self.mirrors))layout.addWidget(custom_radio)custom_edit = QLineEdit()if self.current_mirror and self.get_mirror_name(self.current_mirror) == '自定义源':custom_edit.setText(self.current_mirror)custom_radio.setChecked(True)else:custom_edit.setPlaceholderText('输入镜像源URL,例如: https://pypi.org/simple')layout.addWidget(custom_edit)# 添加单选按钮点击事件def on_radio_clicked(button):if button != custom_radio:url = button.property('url')if url:custom_edit.setText(url)# 连接信号for i in range(mirror_group.buttons().__len__()):button = mirror_group.button(i)if button:button.clicked.connect(lambda checked, btn=button: on_radio_clicked(btn))# 确定和取消按钮btn_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)btn_box.accepted.connect(dialog.accept)btn_box.rejected.connect(dialog.reject)layout.addWidget(btn_box)dialog.setLayout(layout)# 处理对话框结果if dialog.exec() == QDialog.DialogCode.Accepted:selected = mirror_group.checkedButton()if selected == custom_radio and custom_edit.text():self.current_mirror = custom_edit.text()elif selected != custom_radio:self.current_mirror = selected.property('url')# 更新UI和配置self.mirror_label.setText(f'镜像源: {self.get_mirror_name(self.current_mirror)}')self.save_config()# 更新日志self.info_text.clear()self.info_text.append(f"已设置镜像源: {self.get_mirror_name(self.current_mirror)}")self.info_text.append(f"镜像源URL: {self.current_mirror}")self.status_bar.showMessage('镜像源设置成功', 3000)def closeEvent(self, event):"""重写关闭事件,保存配置"""self.save_config()event.accept()def main():"""主程序入口"""app = QApplication(sys.argv)window = PipManager()window.show()sys.exit(app.exec())if __name__ == '__main__':main()