用豆包生成一个python版本切换工具

windows电脑里安装多个版本的python,用替换环境变量的方式实现python版本的切换。

豆包生成的图标:

ppp

环境windows11,python3.13.9,pyside6

文件目录:

image

打包工具pyinstaller

打包命令:pyinstaller.exe --onefile --windowed --name "pvs" --icon "pythonsw128.ico" --add-data "pythonsw128.ico;." 2pypath ps6.py

程序界面:

bk1

豆包给的代码:

import sys
import os
import subprocess
import winreg
import ctypes
from ctypes import wintypes
from PySide6.QtGui import QIcon
from PySide6.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
    QListWidget, QListWidgetItem, QPushButton, QLabel, QMessageBox, QFileDialog
)
from PySide6.QtCore import Qt, QObject, QRunnable, QThreadPool, Signal, Slot

def resource_path(relative_path):
    """获取资源的绝对路径,适用于开发环境和 PyInstaller 打包后的环境"""
    try:
        base_path = sys._MEIPASS
    except Exception:
        base_path = os.path.abspath(".")
    return os.path.join(base_path, relative_path)

class WorkerSignals(QObject):
    finished = Signal()
    error = Signal(str)

class Worker(QRunnable):
    def __init__(self, new_path_string):
        super().__init__()
        self.new_path_string = new_path_string
        self.signals = WorkerSignals()

    @Slot()
    def run(self):
        try:
            # 修改注册表中的环境变量
            reg_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Environment", 0, winreg.KEY_WRITE)
            winreg.SetValueEx(reg_key, "PATH", 0, winreg.REG_EXPAND_SZ, self.new_path_string)
            winreg.CloseKey(reg_key)

            # 发送 WM_SETTINGCHANGE 消息,通知系统环境变量已更改
            HWND_BROADCAST = 0xFFFF
            WM_SETTINGCHANGE = 0x001A
            SMTO_ABORTIFHUNG = 0x0002

            # 调用 Windows API 发送消息
            result = ctypes.windll.user32.SendMessageTimeoutW(
                HWND_BROADCAST,
                WM_SETTINGCHANGE,
                0,
                ctypes.c_wchar_p("Environment"),
                SMTO_ABORTIFHUNG,
                5000,  # 等待 5 秒超时
                None
            )

            if result == 0:
                print("警告:发送环境变量刷新消息失败,可能需要手动重启程序才能生效。")

            # 触发完成信号
            self.signals.finished.emit()

        except Exception as e:
            self.signals.error.emit(f"无法修改环境变量: {str(e)}")

class PythonVersionSwitcher(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Python 版本管理器")
        self.setGeometry(100, 100, 650, 350)

        # 设置窗口图标
        icon_path = resource_path("pythonsw128.ico")
        icon = QIcon(icon_path)
        if not icon.isNull():
            self.setWindowIcon(icon)

        self.scan_directories = [
            r"C:\Program Files",
            r"C:\Program Files (x86)",
            r"C:\Users\Administrator\AppData\Local\Programs\Python",
            r"C:\Python"
        ]
        self.python_versions = {}

        self.threadpool = QThreadPool()
        self.init_ui()
        self.scan_python_versions()

    def init_ui(self):
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        main_layout = QVBoxLayout(central_widget)

        top_layout = QHBoxLayout()
        title_label = QLabel("找到的 Python 版本:")
        title_label.setStyleSheet("font-size: 14px; font-weight: bold;")
        top_layout.addWidget(title_label)

        self.scan_button = QPushButton("重新扫描")
        self.scan_button.clicked.connect(self.scan_python_versions)
        top_layout.addWidget(self.scan_button)

        self.add_dir_button = QPushButton("添加扫描目录")
        self.add_dir_button.clicked.connect(self.add_scan_directory)
        top_layout.addWidget(self.add_dir_button)
        top_layout.addStretch()
        main_layout.addLayout(top_layout)

        self.version_list = QListWidget()
        self.version_list.setSelectionMode(QListWidget.SingleSelection)
        self.version_list.itemSelectionChanged.connect(self.on_selection_changed)
        main_layout.addWidget(self.version_list, 1)

        self.switch_button = QPushButton("切换到选中版本")
        self.switch_button.clicked.connect(self.switch_version)
        self.switch_button.setEnabled(False)
        main_layout.addWidget(self.switch_button)

    def on_selection_changed(self):
        selected_items = self.version_list.selectedItems()
        if not selected_items:
            self.switch_button.setEnabled(False)
            return
        
        selected_text = selected_items[0].text()
        if "未找到" in selected_text or "请点击" in selected_text:
            self.switch_button.setEnabled(False)
        else:
            self.switch_button.setEnabled(True)

    def add_scan_directory(self):
        dir_path = QFileDialog.getExistingDirectory(self, "选择 Python 安装目录的上级目录")
        if dir_path and dir_path not in self.scan_directories:
            self.scan_directories.append(dir_path)
            QMessageBox.information(self, "成功", f"已添加目录: {dir_path}\n将进行重新扫描。")
            self.scan_python_versions()
        elif dir_path:
            QMessageBox.warning(self, "提示", "该目录已在扫描列表中。")

    def _get_current_python_version_from_path(self):
        current_path = self._get_user_path()
        if not current_path:
            return None
        
        for path in current_path.split(';'):
            path = path.strip()
            if not path:
                continue
            if path.lower().endswith("scripts"):
                python_root = os.path.dirname(path)
                python_exe = os.path.join(python_root, "python.exe")
                if os.path.exists(python_exe):
                    try:
                        result = subprocess.run(
                            [python_exe, "--version"],
                            capture_output=True, text=True, check=True, timeout=2
                        )
                        version_output = result.stdout.strip()
                        if version_output.startswith("Python "):
                            return version_output[len("Python "):]
                    except:
                        pass
        return None

    def scan_python_versions(self):
        self.version_list.clear()
        self.python_versions.clear()
        self.scan_button.setEnabled(False)
        self.scan_button.setText("扫描中...")
        self.switch_button.setEnabled(False)

        for root_dir in self.scan_directories:
            if not os.path.isdir(root_dir):
                continue
            for item in os.listdir(root_dir):
                item_path = os.path.join(root_dir, item)
                if os.path.isdir(item_path):
                    python_exe_path = os.path.join(item_path, "python.exe")
                    if os.path.exists(python_exe_path):
                        try:
                            result = subprocess.run(
                                [python_exe_path, "--version"],
                                capture_output=True, text=True, check=True, timeout=2
                            )
                            version_output = result.stdout.strip()
                            if version_output.startswith("Python "):
                                version = version_output[len("Python "):]
                                self.python_versions[version] = item_path
                        except:
                            pass

        current_version = self._get_current_python_version_from_path()

        if self.python_versions:
            for version, path in sorted(self.python_versions.items(), key=lambda x: tuple(map(int, x[0].split('.')[:2]))):
                item_text = f"Python {version} ({path})"
                if version == current_version:
                    item_text += "  **(当前)**"
                QListWidgetItem(item_text, self.version_list)
        else:
            QListWidgetItem("未找到任何 Python 版本。请点击'添加扫描目录'。", self.version_list)

        self.scan_button.setEnabled(True)
        self.scan_button.setText("重新扫描")

    def switch_version(self):
        selected_items = self.version_list.selectedItems()
        if not selected_items:
            return

        selected_text = selected_items[0].text()
        version_str = selected_text.split()[1]

        current_version = self._get_current_python_version_from_path()
        if version_str == current_version:
            QMessageBox.information(self, "提示", f"当前已处于 Python {version_str} 版本。")
            return

        if version_str not in self.python_versions:
            QMessageBox.warning(self, "警告", "所选版本信息无效!")
            return

        python_path = self.python_versions[version_str]
        scripts_path = os.path.join(python_path, "Scripts")

        current_path = self._get_user_path()
        path_list = current_path.split(';') if current_path else []

        non_python_paths = [p.strip() for p in path_list if p.strip() and "python" not in p.strip().lower()]

        new_path_list = [scripts_path, python_path] + non_python_paths
        new_path_string = ';'.join(new_path_list)

        self.switch_button.setEnabled(False)
        self.switch_button.setText("处理中...")

        worker = Worker(new_path_string)
        worker.signals.finished.connect(self.on_switch_finished)
        worker.signals.error.connect(self.on_switch_error)
        self.threadpool.start(worker)

    def on_switch_finished(self):
        QMessageBox.information(
            self, "成功",
            "Python 版本切换成功!\n\n"
            "新的环境变量已自动刷新,重新打开命令行窗口或 IDE 即可生效。"
        )
        self.scan_python_versions()
        self.switch_button.setEnabled(False)
        self.switch_button.setText("切换到选中版本")

    def on_switch_error(self, error_message):
        QMessageBox.critical(self, "错误", error_message)
        self.switch_button.setEnabled(True)
        self.switch_button.setText("切换到选中版本")

    def _get_user_path(self):
        try:
            reg_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Environment", 0, winreg.KEY_READ)
            path_value, _ = winreg.QueryValueEx(reg_key, "PATH")
            winreg.CloseKey(reg_key)
            return path_value
        except FileNotFoundError:
            return ""
        except Exception as e:
            QMessageBox.warning(self, "警告", f"无法读取用户 PATH: {str(e)}")
            return os.environ.get('PATH', '')

    def closeEvent(self, event):
        self.threadpool.waitForDone()
        event.accept()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = PythonVersionSwitcher()
    window.show()
    sys.exit(app.exec())

 

posted @ 2025-11-24 17:02  沛苍冥  阅读(0)  评论(0)    收藏  举报