python库图形管理工具 | pip图形化管理工具(含源码、打包版)

关注IT技术视界公众号,获取好玩有趣软件! 先领1T空间!再存有趣资源!仅有一次机会!

软件由吾爱大佬asdf1233124开发,用于简化Python包(pip)的管理操作。它提供了直观的界面来安装、卸载、升级Python包,并支持镜像源切换功能。

python库 | pip图形化管理工具-第1张图片-IT技术视界

主要功能

包管理功能

  • 包列表展示:显示当前环境中已安装的所有Python包及其版本
  • 包搜索功能:支持本地包搜索和远程PyPI包搜索
  • 包安装:提供对话框输入包名和版本号进行安装
  • 包卸载:可卸载选中的Python包
  • 包升级:可升级选中的Python包到最新版本
  • 包详情查看:显示选中包的详细信息(pip show)

镜像源管理

  • 预设镜像源:内置清华、阿里云、腾讯云和官方源等多个镜像源
  • 自定义镜像源:支持输入自定义镜像源URL
  • 镜像源切换:可随时切换不同的镜像源
  • 镜像源状态显示:在状态栏显示当前使用的镜像源

打包后的代码(适用windows)

关注公众号,获取验证码,验证下载(防止机器采集被和谐)
验证码:
请关注“软件科技派”公众号,回复关键字“验证码”,获取验证码。 【注】在微信里搜索“软件科技派”或者微信扫描右侧二维码都可以关注本站微信公众号。
微信二维码

源码

# -*- coding: utf-8 -*-
"""
pip图形化管理工具主程序
功能:提供图形化界面管理Python包,支持安装/卸载/升级和镜像源切换
"""
 
# 系统模块导入
import sys  # 系统相关功能
import subprocess  # 执行外部命令
import json  # JSON配置文件处理
import os  # 操作系统接口
from datetime import datetime  # 时间处理
 
# PyQt5模块导入
from PyQt5.QtWidgets import *
 
from PyQt5.QtCore import *  # Qt核心功能
from PyQt5.QtGui import *  # 图标处理
 
 
class PipManager(QMainWindow):
    """
    pip图形化管理工具主窗口类
    提供完整的包管理功能界面和逻辑
    """
 
    def __init__(self):
        super().__init__()
 
        self.is_admin = False
        try:
            import ctypes
            self.is_admin = ctypes.windll.shell32.IsUserAnAdmin()
        except:
            pass
         
        # 窗口基本设置
        title = 'pip图形化管理工具'
        if self.is_admin:
            title += ' (管理员)'
        self.setWindowTitle(title)  # 窗口标题
        self.setGeometry(100, 100, 800, 600)  # 窗口位置和大小
         
        # 初始化成员变量
        self.current_mirror = None  # 当前使用的镜像源
        self.installed_packages = []  # 已安装包列表缓存
        self.operation_history = []  # 操作历史记录
        self.searched_packages = [] # 搜索到的包列表
        self.requests_available = False  # 初始化requests可用标志
         
        # 检查requests库是否可用
        try:
            import requests
            self.requests_available = True
        except ImportError:
            self.requests_available = False
            QMessageBox.warning(self, '警告', '需要requests库来搜索远程包,请先安装: pip install requests')
         
        # 初始化UI
        self.init_ui()
         
        # 加载配置
        self.load_config()
         
        # 初始化包列表
        self.refresh_package_list()
        # 初始化右键菜单
        self.init_context_menu()
 
    def init_ui(self):
        """初始化用户界面组件"""
         
        # 1. 创建菜单栏
        self.init_menu_bar()
         
        # 2. 创建工具栏
        self.init_tool_bar()
         
        # 3. 创建状态栏
        self.init_status_bar()
         
        # 4. 创建主内容区
        self.init_main_content()
         
        # 5. 创建右键菜单
        self.init_context_menu()
 
    def init_menu_bar(self):
        """初始化菜单栏"""
         
        # 文件菜单
        file_menu = self.menuBar().addMenu('文件(&F)')
         
        # 刷新操作
        refresh_action = QAction('刷新包列表', self)
        refresh_action.triggered.connect(self.refresh_package_list)
        file_menu.addAction(refresh_action)
         
        # 退出操作
        exit_action = QAction('退出', self)
        exit_action.setShortcut('Ctrl+Q')
        exit_action.triggered.connect(self.close)
        file_menu.addAction(exit_action)
         
        # 设置
        edit_menu = self.menuBar().addMenu('设置(&E)')
         
        # 设置镜像源
        mirror_action = QAction('设置镜像源', self)
        mirror_action.triggered.connect(self.show_mirror_dialog)
        edit_menu.addAction(mirror_action)
 
         # 添加以管理员身份运行选项
        self.admin_action = QAction('以管理员身份运行', self)
        self.admin_action.triggered.connect(self.run_as_admin)
        if self.is_admin:
            self.admin_action.setEnabled(False)  # 已经是管理员则禁用
        edit_menu.addAction(self.admin_action)
         
        # 帮助菜单
        help_menu = self.menuBar().addMenu('帮助(&H)')
         
        # 关于操作
        about_action = QAction('关于', self)
        about_action.triggered.connect(self.show_about)
        help_menu.addAction(about_action)
 
    def run_as_admin(self):
        """以管理员身份重新运行程序"""
        try:
            import ctypes
            import sys
             
            # 检查是否已经是管理员权限
            if ctypes.windll.shell32.IsUserAnAdmin():
                QMessageBox.information(self, '提示', '当前已经是管理员权限')
                return
                 
            # 重新以管理员权限运行
            ctypes.windll.shell32.ShellExecuteW(
                None, "runas", sys.executable, " ".join(sys.argv), None, 1
            )
             
            # 退出当前实例
            self.close()
             
        except Exception as e:
            QMessageBox.critical(self, '错误', f'请求管理员权限失败: {str(e)}')
 
 
    def init_tool_bar(self):
        """初始化工具栏"""
         
        toolbar = QToolBar('主工具栏')
        toolbar.setIconSize(QSize(16, 16))
        self.addToolBar(toolbar)
         
        # 安装按钮
        install_btn = QPushButton('安装包')
        install_btn.clicked.connect(self.install_package)
        toolbar.addWidget(install_btn)
         
        # 卸载按钮
        uninstall_btn = QPushButton('卸载包')
        uninstall_btn.clicked.connect(self.uninstall_package)
        toolbar.addWidget(uninstall_btn)
         
        # 升级按钮
        upgrade_btn = QPushButton('升级包')
        upgrade_btn.clicked.connect(self.upgrade_package)
        toolbar.addWidget(upgrade_btn)
         
        # 刷新按钮
        refresh_btn = QPushButton('刷新')
        refresh_btn.clicked.connect(self.refresh_package_list)
        toolbar.addWidget(refresh_btn)
 
        # 设置镜像源
        jxy_btn = QPushButton('设置镜像源')
        jxy_btn.clicked.connect(self.show_mirror_dialog)
        toolbar.addWidget(jxy_btn)
 
    def init_status_bar(self):
        """初始化状态栏"""
         
        self.status_bar = QStatusBar()
        self.setStatusBar(self.status_bar)
         
        # 显示当前镜像源
        self.mirror_label = QLabel('镜像源: 未设置')
        self.status_bar.addPermanentWidget(self.mirror_label)
         
        # 显示状态信息
        self.status_bar.showMessage('准备就绪', 3000)
 
    def init_main_content(self):
        """初始化主内容区域"""
         
        main_widget = QWidget()
        self.setCentralWidget(main_widget)
         
        # 主布局
        main_layout = QHBoxLayout()
        main_widget.setLayout(main_layout)
         
        # 左侧布局 (包列表)
        left_layout = QVBoxLayout()
         
        # 搜索框
        self.search_box = QLineEdit()
        self.search_box.setPlaceholderText('搜索包名...')
        self.search_box.textChanged.connect(self.filter_packages)
        left_layout.addWidget(self.search_box)
         
        # 包列表
        self.package_list = QListWidget()
        self.package_list.setAlternatingRowColors(True)
        left_layout.addWidget(self.package_list)
         
        # 右侧布局 (包详情)
        right_layout = QVBoxLayout()
         
        # 包详情标签
        self.package_detail = QTextEdit()
        self.package_detail.setReadOnly(True)
        right_layout.addWidget(self.package_detail)
         
        # 添加到主布局
        main_layout.addLayout(left_layout, 3)  # 左侧占3份
        main_layout.addLayout(right_layout, 2)  # 右侧占2份
 
    def init_context_menu(self):
        """初始化右键菜单"""
         
        # 设置包列表的右键菜单
        self.package_list.setContextMenuPolicy(Qt.CustomContextMenu)
        self.package_list.customContextMenuRequested.connect(self.show_package_context_menu)
         
        # 创建菜单对象
        self.package_menu = QMenu(self)
         
        # 添加菜单项
        install_action = QAction('安装', self)
        install_action.triggered.connect(self.install_selected_package)  # 改为安装选中包
        self.package_menu.addAction(install_action)
         
        uninstall_action = QAction('卸载', self)
        uninstall_action.triggered.connect(self.uninstall_package)
        self.package_menu.addAction(uninstall_action)
         
        upgrade_action = QAction('升级', self)
        upgrade_action.triggered.connect(self.upgrade_package)
        self.package_menu.addAction(upgrade_action)
         
        self.package_menu.addSeparator()
         
        info_action = QAction('查看详情', self)
        info_action.triggered.connect(self.show_package_info)
        self.package_menu.addAction(info_action)
    def install_selected_package(self):
        """安装选中的包"""
        selected_item = self.package_list.currentItem()
        if not selected_item:
            QMessageBox.warning(self, '警告', '请先选择一个包')
            return
             
        pkg_name = selected_item.text().split('==')[0]
         
        # 显示使用的镜像源信息
        mirror_info = ""
        if self.current_mirror:
            mirror_info = f"\n镜像源: {self.get_mirror_name(self.current_mirror)}"
         
        # 确认安装
        confirm = QMessageBox.question(
            self, '确认安装',
            f'确定要安装 {pkg_name} 吗?{mirror_info}',
            QMessageBox.Yes | QMessageBox.No
        )
         
        if confirm == QMessageBox.Yes:
            try:
                self.run_pip_command('install', pkg_name)
            except Exception as e:
                QMessageBox.critical(self, '安装错误', f'安装过程中出错\n{str(e)}')
 
    def load_config(self):
        """加载配置文件"""
         
        config_path = os.path.expanduser('~/.pip-gui-config')
         
        # 默认镜像源配置
        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"
        }
         
        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')
                    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-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 name
        return '自定义源'
 
    def refresh_package_list(self):
        """刷新已安装包列表"""
         
        try:
            # 执行pip list命令获取已安装包
            result = subprocess.run(
                [sys.executable, '-m', 'pip', 'list', '--format=json'],
                capture_output=True, text=True
            )
             
            if result.returncode == 0:
                # 解析JSON结果
                self.installed_packages = json.loads(result.stdout)
                self.update_package_list_display()
                self.status_bar.showMessage('包列表刷新成功', 3000)
            else:
                raise Exception(result.stderr)
                 
        except Exception as 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']}")
            item.setForeground(QColor('black'))  # 本地包用黑色
            self.package_list.addItem(item)
         
        # 添加搜索到的包(如果有)
        for pkg in self.searched_packages:
            item = QListWidgetItem(f"{pkg['name']}=={pkg['version']} (可安装)")
            item.setForeground(QColor('blue'))  # 可安装包用蓝色
            self.package_list.addItem(item)
 
 
    def search_remote_package(self, package_name):
        """搜索远程包"""
        try:
            import requests
            print(f"正在搜索: {package_name}")  # 调试输出
             
            # 设置正确的请求头
            headers = {
                "Accept": "application/json",
                "User-Agent": "pip-gui-tool/1.0"
            }
             
            # 使用PyPI的JSON API端点
            response = requests.get(
                f"https://pypi.org/pypi/{package_name}/json",
                headers=headers,
                timeout=10
            )
             
            print(f"响应状态码: {response.status_code}")  # 调试输出
            print(f"响应内容: {response.text[:200]}...")  # 只打印前200字符
             
            # 检查响应状态
            response.raise_for_status()
                 
            data = response.json()
             
            self.searched_packages = []
            # 解析API返回的数据格式
            if 'info' in data:
                self.searched_packages.append({
                    'name': data['info']['name'],
                    'version': data['info'].get('version', '最新')
                })
                     
            if self.searched_packages:
                self.update_package_list_display()
                self.status_bar.showMessage(f"找到包: {self.searched_packages[0]['name']}", 3000)
            else:
                self.status_bar.showMessage("未找到匹配包", 3000)
                 
        except requests.exceptions.HTTPError as e:
            if e.response.status_code == 404:
                self.status_bar.showMessage(f"未找到包: {package_name}", 3000)
            else:
                error_msg = f"网络请求失败: {str(e)}"
                print(error_msg)
                QMessageBox.critical(self, '错误', error_msg)
        except Exception as e:
            error_msg = f"搜索失败: {str(e)}"
            print(error_msg)
            QMessageBox.critical(self, '错误', error_msg)
 
 
 
    def filter_packages(self):
        """根据搜索框内容过滤包列表"""
        search_text = self.search_box.text().strip()
         
        if not search_text:
            self.searched_packages = []
            self.update_package_list_display()
            return
             
        # 如果requests不可用,直接返回
        if not self.requests_available:
            QMessageBox.warning(self, '警告', 'requests库未安装,无法搜索远程包')
            return
             
        # 先显示本地匹配项
        has_local_match = False
        for i in range(self.package_list.count()):
            item = self.package_list.item(i)
            match = search_text.lower() in item.text().lower()
            item.setHidden(not match)
            if match:
                has_local_match = True
                 
        # 如果没有本地匹配,才进行远程搜索
        if not has_local_match:
            self.search_remote_package(search_text)
 
 
 
 
    def show_package_context_menu(self, pos):
        """显示包列表的右键菜单"""
         
        # 确保有选中的项才显示菜单
        if self.package_list.currentItem():
            self.package_menu.exec_(self.package_list.mapToGlobal(pos))
 
    def show_package_info(self):
        """显示选中包的详细信息"""
         
        selected_item = self.package_list.currentItem()
        if not selected_item:
            return
             
        pkg_name = selected_item.text().split('==')[0]
         
        try:
            # 执行pip show命令获取包详情
            result = subprocess.run(
                [sys.executable, '-m', 'pip', 'show', pkg_name],
                capture_output=True, text=True
            )
             
            if result.returncode == 0:
                self.package_detail.setText(result.stdout)
            else:
                raise Exception(result.stderr)
                 
        except Exception as e:
            QMessageBox.critical(self, '错误', f'获取包信息失败: {str(e)}')
 
    def install_package(self):
        """安装Python包"""
        # 创建自定义对话框
        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.Ok | QDialogButtonBox.Cancel)
        btn_box.accepted.connect(dialog.accept)
        btn_box.rejected.connect(dialog.reject)
        layout.addRow(btn_box)
         
        dialog.setLayout(layout)
         
        if dialog.exec_() == QDialog.Accepted:
            pkg_name = pkg_name_edit.text().strip()
            version = version_edit.text().strip()
             
            if not pkg_name:
                QMessageBox.warning(self, '警告', '包名不能为空')
                return
                 
            if version and not all(c.isdigit() or c == '.' for c in version):
                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.Yes | QMessageBox.No
            )
             
            if confirm == QMessageBox.Yes:
                try:
                    self.run_pip_command('install', pkg_name, version)
                except Exception as e:
                    if "Could not find a version" in str(e):
                        QMessageBox.warning(self, '版本错误', f"找不到指定版本的包\n{e}")
                    elif "No matching distribution" in str(e):
                        QMessageBox.warning(self, '包名错误', f"找不到指定的包\n{e}")
                    else:
                        QMessageBox.critical(self, '安装错误', f"安装过程中出错\n{e}")
 
    def uninstall_package(self):
        """卸载Python包"""
         
        selected_item = self.package_list.currentItem()
        if not selected_item:
            QMessageBox.warning(self, '警告', '请先选择一个包')
            return
             
        pkg_name = selected_item.text().split('==')[0]
         
        # 确认卸载
        confirm = QMessageBox.question(
            self, '确认卸载',
            f'确定要卸载 {pkg_name} 吗?',
            QMessageBox.Yes | QMessageBox.No
        )
         
        if confirm == QMessageBox.Yes:
            self.run_pip_command('uninstall', pkg_name)
 
    def upgrade_package(self):
        """升级Python包"""
         
        selected_item = self.package_list.currentItem()
        if not selected_item:
            QMessageBox.warning(self, '警告', '请先选择一个包')
            return
             
        pkg_name = selected_item.text().split('==')[0]
         
        # 确认升级
        confirm = QMessageBox.question(
            self, '确认升级',
            f'确定要升级 {pkg_name} 吗?',
            QMessageBox.Yes | QMessageBox.No
        )
         
        if confirm == QMessageBox.Yes:
            self.run_pip_command('install', pkg_name, upgrade=True)
 
    def run_pip_command(self, command, pkg_name, version=None, upgrade=False):
        """执行pip命令的通用方法"""
         
         # 构建命令
        cmd = [sys.executable, '-m', 'pip', command, pkg_name]
         
        # 卸载时自动确认
        if command == 'uninstall':
            cmd.append('--yes')
         
        # 添加版本号
        if version:
            cmd.append(f'=={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])
         
        try:
            # 执行命令并实时输出
            process = subprocess.Popen(
                cmd,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True,
                bufsize=1,
                universal_newlines=True
            )
             
            # 清空并准备显示输出
            self.package_detail.clear()
             
            # 实时读取输出
            while True:
                output = process.stdout.readline()
                if output == '' and process.poll() is not None:
                    break
                if output:
                    self.package_detail.append(output.strip())
             
            # 获取最终返回码
            return_code = process.wait()
             
            # 记录操作历史
            self.operation_history.append({
                'command': ' '.join(cmd),
                'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
                'success': return_code == 0,
                'output': self.package_detail.toPlainText()
            })
             
            # 限制历史记录数量
            if len(self.operation_history) > 20:
                self.operation_history.pop(0)
             
            # 显示结果
            if return_code == 0:
                QMessageBox.information(
                    self, '操作成功',
                    f'操作执行成功'
                )
                self.refresh_package_list()
            else:
                error_output = process.stderr.read()
                self.package_detail.append(f"\n错误信息:\n{error_output}")
                raise Exception(error_output)
                 
        except Exception as e:
            QMessageBox.critical(
                self, '操作失败',
                f'操作执行失败:\n{str(e)}'
            )
            self.package_detail.append(f"\n错误信息:\n{str(e)}")
 
 
    def show_mirror_dialog(self):
        """显示镜像源设置对话框"""
         
        # 创建对话框
        dialog = QDialog(self)
        dialog.setWindowTitle('设置镜像源')
        layout = QVBoxLayout()
         
        # 添加镜像源选项
        mirror_group = QButtonGroup()
        for i, (name, url) in enumerate(self.mirrors.items()):
            radio = QRadioButton(name)
            radio.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()
        # 修改这里:总是显示当前镜像源URL(如果有)
        if self.current_mirror:
            custom_edit.setText(self.current_mirror)
        else:
            custom_edit.setPlaceholderText('输入镜像源URL')
         
        # 设置默认选中状态
        if not self.current_mirror:
            # 如果没有设置镜像源,默认选中第一项
            mirror_group.buttons()[0].setChecked(True)
            custom_edit.setText(mirror_group.buttons()[0].url)
         
        layout.addWidget(custom_edit)
         
        # 添加单选按钮点击事件
        def on_radio_clicked(button):
            if button != custom_radio:
                custom_edit.setText(button.url)
         
        mirror_group.buttonClicked.connect(on_radio_clicked)
         
        # 确定和取消按钮
        btn_box = QDialogButtonBox(
            QDialogButtonBox.Ok | QDialogButtonBox.Cancel
        )
        btn_box.accepted.connect(dialog.accept)
        btn_box.rejected.connect(dialog.reject)
        layout.addWidget(btn_box)
         
        dialog.setLayout(layout)
         
        # 处理对话框结果
        if dialog.exec_() == QDialog.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.url
            else:
                return
                 
            # 更新UI和配置
            self.mirror_label.setText(f'镜像源: {self.get_mirror_name(self.current_mirror)}')
            self.save_config()
            self.status_bar.showMessage('镜像源设置成功', 3000)
 
 
 
 
 
    def show_about(self):
        """显示关于对话框"""
         
        QMessageBox.about(
            self, '关于 pip图形化管理工具',
            'pip图形化管理工具 v1.0\n\n'
            '一个简单的pip包管理图形界面\n'
            '支持安装/卸载/升级和镜像源切换'
        )
 
    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()  # 只有直接运行时才执行

 

THE END