python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > Python多版本管理工具

在Windows系统下管理多版本Python的工具实现方案

作者:薛定谔的猫喵喵

作为一名开发者,我的电脑上常年安装着多个 Python 版本:2.7、3.7、3.8、3.9、3.10……每次需要切换时,要么手动修改系统环境变量 PATH,因此本文记录了我从编写 PowerShell 脚本、封装成全局命令,到解决执行策略限制的完整过程,需要的朋友可以参考下

手把手打造 Python 多版本管理工具,告别 PATH 混乱,轻松切换

写在前面

作为一名开发者,我的电脑上常年安装着多个 Python 版本:2.7、3.7、3.8、3.9、3.10……每次需要切换时,要么手动修改系统环境变量 PATH,要么在项目里硬编码路径,烦不胜烦。早就羡慕 Linux 下的 nvm(Node Version Manager)和 pyenv,于是决定在 Windows 上也造一个类似的轮子。

本文记录了我从编写 PowerShell 脚本、封装成全局命令,到解决执行策略限制的完整过程。最终的效果是:在 PowerShell 中直接输入 pvm listpvm use 3.9.10,就能像使用内置命令一样管理 Python 版本。

一、方案选择:PowerShell 函数 + 全局调用

实现一个“全局命令”通常有三种方式:

将脚本所在目录添加到 PATH 环境变量
最正统的做法,任何命令行都能识别。但需要处理管理员权限(修改系统 PATH)以及脚本执行策略。

在 PowerShell Profile 中定义函数
仅在 PowerShell 中有效,但无需修改 PATH,权限要求低。适合纯 PowerShell 用户。

编写批处理包装器 + 添加 PATH
兼容 CMD 和 PowerShell,但同样涉及 PATH 修改和提权。

考虑到我主要使用 PowerShell,且希望操作尽量简单,我选择了 方案二:在 PowerShell Profile 中定义一个函数 pvm,该函数调用我的核心脚本 pvm.ps1。这样只需要把脚本放在固定位置,然后在 profile 中写一行函数定义,即可在任何 PowerShell 窗口中使用 pvm 命令。

二、核心脚本:pvm.ps1

首先编写核心逻辑的 PowerShell 脚本。脚本的功能包括:

完整代码如下,你只需修改 $pythonVersions 哈希表中的路径为你自己的实际安装目录即可。

# pvm.ps1 - Python Version Manager for Windows
param(
    [string]$Command,
    [string]$Argument
)
# 要求以管理员身份运行(因为要修改系统 PATH)
if (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
    Write-Error "此脚本需要以管理员身份运行。请右键点击 PowerShell 并选择「以管理员身份运行」,然后重试。"
    exit 1
}
# ---------- 配置区域:请根据您的实际安装路径修改 ----------
$pythonVersions = @{
    "2.7.3" = @{
        Path    = "D:\Develop\python-2.7.3"
        Scripts = "D:\Develop\python-2.7.3\Scripts"
    }
    "3.7.4" = @{
        Path    = "D:\Develop\Python37"
        Scripts = "D:\Develop\Python37\Scripts"
    }
    "3.7.9" = @{
        Path    = "D:\Develop\Python-3.7.9"
        Scripts = "D:\Develop\Python-3.7.9\Scripts"
    }
    "3.8.10" = @{
        Path    = "D:\Develop\python-3.8.10"
        Scripts = "D:\Develop\python-3.8.10\Scripts"
    }
    "3.9.10" = @{
        Path    = "D:\Develop\python-3.9"
        Scripts = "D:\Develop\python-3.9\Scripts"
    }
    "3.10.8" = @{
        Path    = "D:\Develop\python-3.10.8"
        Scripts = "D:\Develop\python-3.10.8\Scripts"
    }
}
# ---------------------------------------------------------
# 收集所有已知的 Python 相关路径(用于后续过滤)
$allPythonPaths = @()
foreach ($ver in $pythonVersions.Keys) {
    $allPythonPaths += $pythonVersions[$ver].Path
    $allPythonPaths += $pythonVersions[$ver].Scripts
}
$allPythonPaths = $allPythonPaths | Where-Object { $_ -ne '' } | ForEach-Object { [System.IO.Path]::GetFullPath($_) }
# 获取系统 PATH(机器级别)
function Get-SystemPath {
    return [Environment]::GetEnvironmentVariable("Path", "Machine") -split ';' | Where-Object { $_ -ne '' }
}
# 设置系统 PATH
function Set-SystemPath($paths) {
    $newPath = $paths -join ';'
    [Environment]::SetEnvironmentVariable("Path", $newPath, "Machine")
    $env:Path = $newPath   # 立即更新当前会话
}
# 获取当前正在使用的 Python 版本(如果属于管理列表则返回版本号,否则返回 $null)
function Get-CurrentPythonVersion {
    try {
        $pythonExe = (Get-Command python -ErrorAction Stop).Source
    } catch {
        Write-Warning "当前 PATH 中未找到 python 命令。"
        return $null
    }
    $pythonExe = [System.IO.Path]::GetFullPath($pythonExe)
    foreach ($version in $pythonVersions.Keys) {
        $info = $pythonVersions[$version]
        $pythonDir = [System.IO.Path]::GetFullPath($info.Path)
        $expectedExe = Join-Path $pythonDir "python.exe"
        if ($pythonExe -eq $expectedExe) {
            return $version
        }
    }
    return $null
}
# 获取当前 Python 的完整路径(用于显示)
function Get-CurrentPythonPath {
    try {
        return (Get-Command python -ErrorAction Stop).Source
    } catch {
        return $null
    }
}
# 列出所有版本,并标记当前版本
function List-Versions {
    $current = Get-CurrentPythonVersion
    Write-Host "已安装的 Python 版本:"
    foreach ($version in ($pythonVersions.Keys | Sort-Object)) {
        if ($version -eq $current) {
            Write-Host "* $version (当前)" -ForegroundColor Green
        } else {
            Write-Host "  $version"
        }
    }
}
# 切换到指定版本
function Use-Version($version) {
    if (-not $pythonVersions.ContainsKey($version)) {
        Write-Host "错误:版本 '$version' 不存在。" -ForegroundColor Red
        return $false
    }
    $current = Get-CurrentPythonVersion
    if ($current -eq $version) {
        Write-Host "当前已经是 Python $version,无需切换。" -ForegroundColor Yellow
        return $true
    }
    # 新版本需要添加的路径
    $newPaths = @($pythonVersions[$version].Path, $pythonVersions[$version].Scripts) | Where-Object { $_ -ne '' }
    $newPaths = $newPaths | ForEach-Object { [System.IO.Path]::GetFullPath($_) }
    # 获取当前系统 PATH
    $currentPath = Get-SystemPath
    # 过滤掉所有已知的 Python 路径
    $filteredPath = $currentPath | Where-Object {
        $item = $_
        try {
            $fullItem = [System.IO.Path]::GetFullPath($item)
        } catch {
            $fullItem = $item
        }
        $matched = $false
        foreach ($known in $allPythonPaths) {
            if ($fullItem -eq $known) {
                $matched = $true
                break
            }
        }
        -not $matched
    }
    # 构建新 PATH:新路径在前,其余在后
    $newPathList = $newPaths + $filteredPath
    Set-SystemPath $newPathList
    Write-Host "已切换到 Python $version。请重新打开命令行窗口以使更改完全生效。" -ForegroundColor Green
    return $true
}
# 主命令处理
if (-not $Command) {
    # 交互模式
    Write-Host "Python Version Manager (pyvm)"
    while ($true) {
        List-Versions
        $selected = Read-Host "`n请输入要切换的版本号 (输入 q 退出)"
        if ($selected -eq 'q') { exit }
        $result = Use-Version $selected
        if ($result) {
            $continue = Read-Host "是否继续操作其他版本?(y/n)"
            if ($continue -ne 'y') { exit }
        }
    }
} else {
    switch ($Command.ToLower()) {
        'list' {
            List-Versions
        }
        'current' {
            $currentVer = Get-CurrentPythonVersion
            $currentPath = Get-CurrentPythonPath
            if ($currentVer) {
                Write-Host "当前 Python 版本: $currentVer"
            } elseif ($currentPath) {
                Write-Host "当前使用的 Python 不在管理列表中,路径为: $currentPath" -ForegroundColor Yellow
            } else {
                Write-Host "当前未检测到 Python 命令。" -ForegroundColor Red
            }
        }
        'use' {
            if (-not $Argument) {
                Write-Error "请指定要使用的版本号。"
                exit 1
            }
            $result = Use-Version $Argument
            if (-not $result) { exit 1 }
        }
        default {
            Write-Error "未知命令: $Command。可用命令: list, current, use <version>"
            exit 1
        }
    }
}

将上述代码保存为 pvm.ps1,放在一个固定路径,例如 D:\Develop\pvm\pvm.ps1

三、将 pvm 变为全局命令:PowerShell 函数

现在我们需要在 PowerShell 的配置文件中定义一个函数 pvm,它负责调用 pvm.ps1 并传递参数。

打开 PowerShell Profile
在 PowerShell 中执行:

notepad $PROFILE

如果提示文件不存在,选择“是”创建。

添加函数定义
在打开的记事本中,输入以下内容(注意修改路径为你的 pvm.ps1 实际位置):

function pvm { & "D:\Develop\pvm\pvm.ps1" @args }

保存并关闭文件。

重新加载 Profile
在 PowerShell 中执行:

. $PROFILE

四、遇到的第一道坎:执行策略限制

执行 . $PROFILE 时,你可能遇到这样的错误:

. : 无法加载文件 D:\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1,因为在此系统上禁止运行脚本。有关详细信息,请参阅 https:/go.microsoft.com/fwlink/?LinkID=135170 中的 about_Execution_Policies。

这是因为 PowerShell 默认的执行策略是 Restricted,禁止运行任何脚本(包括 profile 文件)。解决办法是修改执行策略。

解决方法

以管理员身份打开 PowerShell(右键开始菜单 → Windows PowerShell (管理员))。

执行以下命令,将策略设置为 RemoteSigned

Set-ExecutionPolicy RemoteSigned

如果只想对当前用户生效,可以加 -Scope CurrentUser

Set-ExecutionPolicy -Scope CurrentUser RemoteSigned

按提示输入 Y 确认。

现在再次加载 profile:. $PROFILE,应该就不会报错了。

解释RemoteSigned 策略允许运行本地未签名的脚本,但会阻止从互联网下载的未签名脚本,既满足了我们运行本地脚本的需求,又兼顾了安全性。

五、使用 pvm 命令

一切就绪后,我们可以在 PowerShell 中愉快地使用 pvm 了。

列出所有版本

pvm list

输出示例:

已安装的 Python 版本:
  2.7.3
  3.7.4
  3.7.9
  3.8.10
  3.9.10
* 3.10.8 (当前)

查看当前版本

pvm current

切换到指定版本(需要管理员权限):

pvm use 3.9.10

由于脚本需要修改系统 PATH,请确保 PowerShell 是以管理员身份运行的。如果当前不是管理员,可以右键点击 PowerShell 图标,选择“以管理员身份运行”,然后再执行 pvm use

交互式模式

pvm

进入交互菜单,显示版本列表并提示输入版本号切换。

六、进阶:自动提权(可选)

每次切换都要手动以管理员身份运行 PowerShell 挺麻烦的。我们可以在脚本中集成自动提权逻辑,当检测到没有管理员权限时,自动弹出 UAC 窗口以管理员身份重启脚本。

将 pvm.ps1 开头的权限检查替换为以下代码:

# 检查并自动提权
if (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
    Write-Host "正在请求管理员权限..." -ForegroundColor Yellow
    Start-Process powershell -Verb RunAs -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`" $($MyInvocation.Line.Substring($MyInvocation.Line.IndexOf(' ') + 1))"
    exit
}

这样,当你执行 pvm use 3.9.10 且当前 PowerShell 不是管理员时,会自动弹出一个 UAC 确认窗口,确认后会在新的管理员窗口中执行切换操作。切换完成后新窗口会关闭,原窗口仍保持。

七、常见问题

1. 切换后python --version还是旧版本?

2. 修改执行策略后仍然报错?

3. 切换时提示“找不到版本”?

八、总结

通过 PowerShell 函数 + 核心脚本的方式,我们成功在 Windows 上实现了一个轻量级的 Python 版本管理器。核心优势是:

如果你也在 Windows 下被多 Python 版本困扰,不妨试试这个方案。所有代码已在 Windows 10/11、PowerShell 5.1 和 7+ 上测试通过。欢迎评论区交流改进!

附:完整项目文件结构

D:\Develop\pvm\
  ├── pvm.ps1          # 核心脚本
  └── README.md        # 说明文档(可选)

Profile 配置

# Microsoft.PowerShell_profile.ps1
function pvm {
    & "D:\Develop\pvm\pvm.ps1" @args
}

以上就是在Windows系统下管理多版本Python的工具实现方案的详细内容,更多关于Python多版本管理工具的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文