@snownstone

Python 虚拟环境

Published on

补丁补丁又补丁,几番周折后,现在觉得撇开 pyenv,单独安装使用 conda 也挺好。如果 P 能始终无痛且完整同步地管着 C,当然是最有序的;但如果出现冲突或 P 的管束会影响限制 C 的发挥使用,那还不如解绑各自独立,虽然关系上感觉会混乱些,但我系统内的混乱多了去了,硬让它俩干净有序又如何。当下还先继续这样用,即 pyenv 全都统一管着,后面如果又有问题再说,真不行就重新独立安装 conda,克隆当前环境。(1222 补)


目录

啰嗦预警

这篇博客的原始版本还没有发布,因为我换了工具,于是加了一个 啰嗦补丁(已被 啰嗦预警 替代),结果沿着那个思路还没有重新整理完,又有了惊人的新发现,二次反转来了,于是就补丁落补丁,有了这个 啰嗦预警

核心问题就是,使用什么工具创建、管理 Python 的虚拟环境?

Python 本身其实自带 venv 模块可以用来创建虚拟环境,但是一方面它的功能可能相对基础,另一方面这个 Python 是什么 Python,版本是多少?在一个操作系统中,根据不同的项目需求,也为了不污染影响系统本身的 Python 环境,肯定还是要另外安装使用其它 Python 。其它这些 Python 用什么工具安装管理,如何基于它们创建虚拟环境,如何管理虚拟环境中安装的各种新的依赖。只靠 venv 显然是不能解决这些问题的,还需要其他工具。

我最开始使用 Python 做项目的时候,当时选用的就是 pyenv,那时的背景和考量如下:

关于选用 pyenv 管理 python 版本的笔记截图

当时重点参照了 real-python 上这篇介绍 pyenv 的教程:

所以很自然地,如今也还是继续用它,这篇文章的初始版本也是在这样的背景下写的,工具的选择没有变,只是对它们解决的问题有了更深入的理解。

如下图所示,我又用 pyenv 安装了一个新的 Python 3.12.0,并用 pyenv-virtualenv 插件基于这个 Python 创建了新的虚拟环境,并在其中安装 JupyterLab

使用 pyenv 管理系统内 python 及其虚拟环境的关系图

因为 JupyterLab 的依赖很多,而且过程中出了点问题,我又看了其他一些资料,为了克服 pip 通过生成 requirements.txt 管理依赖的漏洞问题,就决定换用 conda

这是第一次转折。

参照着 Miniconda 的官方安装指南,我卸载了 Python 3.12.0,安装了 conda,并用它创建了虚拟环境 base_py

使用 conda 管理 python 环境

由此,我以为自己从 pyenv 切换到了 conda

结果,就在我根据这次转变重新整理这篇博客的时候,无意中瞄到 pyenv-virtualenv 主页使用说明上说它们也可以安装管理 conda 环境。我就非常震惊,怎么会这样,之前怎么没看到,我以为自己跟它倆说拜拜了,结果它们让我站住并回来...

这是第二次转折。

我当然要卸掉刚独立安装的 miniconda 再用 pyenv 重新安装一次。虽然麻烦,但是如此一来,整个操作系统内所有的 Python 环境全都可以交给 pyenv 统一安装管理,对此我感到非常好受。相比之下,重复安装那点麻烦不算什么,我要它们整整齐齐的。

于是,虽然标题是关于 Python 的虚拟环境,到头来,整篇文章感觉就是在夸 pyenv 大法好。

我不觉得自己的反应是小题大作,我的惊讶有着充分的理由...

首先,conda 对虚拟环境的创建管理和 virtualenv 或基于它的其它工具不同。每一个 conda 虚拟环境中都装有一个 完整 的 Python。

另外,conda 可以替代 pip 安装 Python 甚至其他语言的包,相比 pyenvpoetrypipenv 这些 p 打头的 Python 工具,之前总感觉它有一个自己的生态,两方阵营是择其一使用的关系。特别是在研究 conda 时,看到了 这篇博客 。其中讲到 conda 是从 Python 数据/科学 Python(PyData/SciPy) 生态中产生的,这个生态与纯 Python 开发生态还是有区别的,作者通过厘清围绕 conda 的迷思与误解,异常清晰地对这两条线的关系做了梳理和解释。例如,数据科学中常用的 NumPy 包可能包含 Python 外的其他依赖,并不是纯 Python 开发的,这就超出了 pip 的依赖管理范畴和能力,用 conda 安装会更好。具体见文中 迷思 4:开发 Conda 是不负责任的分裂 Python 的行为吗?

因为以上这些 context,当我发现 pyenv 同样可以安装管理 conda 时,才会有转折再转折的惊讶。

其实实现原理并不复杂,因为 pyenv 只是帮忙下载并运行 miniconda 的安装脚本,将其存放路径统一在自己的 Python 路径管理下。但它就是把一件事以简单的方式做得非常完备,我很喜欢。

下面其实才是原本的正文内容,参照 Python Virtual Environments: A Primer 这篇教程理解 Python 的虚拟环境,梳理 pyenv 以及 pyenv-virtualenv 的基本原理和使用方法,最后再补上 conda 的基本使用方法。


为什么需要创建和使用虚拟环境?

因为 Python 的依赖管理不好,pip 会把所有外部依赖全都默认安装在操作系统自带的 Python(后面都简称为 系统 Python)的 site-packages 中。

可能的影响:

  • 系统污染

    • 因为 Linux 系统自带预装 Python,用户后来安装的外部依赖有可能会干扰操作系统本身的功能和运行

    • 操作系统更新时,也有可能抹除或覆盖用户自己安装的依赖包

  • 依赖冲突

    • 不同的项目可能需要不同版本的同一外部依赖,如果都安装在同一个位置,自然无法同时安装两个不同的版本
  • 项目的可复现性(reproducibility)

    • 如果依赖全都安在同一个位置,就很难把某一个项目所需的依赖清晰择出作为整体独立环境分享出去
  • 缺少安装依赖的系统管理员权限

    • 不如使用自己的普通用户权限创建独立使用的虚拟环境

虚拟环境是什么?

Python 虚拟环境实质就是一个自包含的文件夹结构(folder structure),内含创建该虚拟环境的那个 Python (后面简称 基础 Python)的可执行文件的副本或 symlinks,最核心的构成包括:

  • Python binary:如 Python 解释器、pip 及它们各自的 symlinks 等

  • pyvenv.cfg 文件:含有一些键值対,用来在 sys 模块中设置变量,决定使用哪个 Python 解释器以及 site-packages 路径

  • site-packages 路径:自带的(默认 pipsetuptools(pip 的依赖))以及该虚拟环境后续安装的各种外部依赖

注:Python 的标准库(standard library)并没有出现在虚拟环境中,这部分是直接复用安装该虚拟环境的 基础 Python,这也是为什么虚拟环境更轻量、以及适合根据需求速建速删。

经设置,基础 Pythonsite-packages 也可以开放给虚拟环境使用,但是是单向性的,虚拟环境安装的外部依赖仍然是和 基础 Pythonsite-packages 严格分离的。

虚拟环境如何工作?

  • 复制 基础 Python 的构成结构与文件

  • 修改前缀查找过程

    • 不同于 基础 Python,虚拟环境中的 Python 解释器不去寻找 os 模块来确定标准库的位置,而是先搜索 pyvenv.cfg 文件。如果这个文件中存有 home 键,就会用它来设定两个变量的值:

      • sys.prefix 指向含有 pyvenv.cfg 文件的路径(即虚拟环境对应路径)

      • sys.base_prefix 对应创建该虚拟环境的 基础 Python 可执行文件所在路径

      python 中查看 sys.base_prefix 与 sys.prefix 指向不同的路径

      3.8.7 是我先用 pyenv 安装的 基础 Python,然后又使用 pyenv-virtualenv 插件,通过这个 基础 Python 构建了虚拟环境 BeatsTracking。)

      于是,标准库相关的文件会以 sys.base_prefix 为准去 基础 Python 对应路径寻找;而 site-packages 路径则以 sys.prefix 为准在虚拟环境中查找。

  • 链接回 标准库

    • 前面其实已提及,虚拟环境的设计就是尽可能轻量,所以只复制最少的必要文件,即 二进制 可执行文件的副本或 symlink、pyvenv.cfg 文件和 site-packages 路径。虚拟环境中的 Python 解释器可以访问标准库内的模块,通过 pyvenv.cfg 文件中指定的 home 路径:

      pyvenv.cfg文件规定的home路径

      home 路径指向了创建 BeatsTracking 虚拟环境的 基础 Python 的可执行文件;当前第二行表示 基础 Python 安装的软件 (site-packages)不对虚拟环境解释器开放使用;如果第二行设为 True,那么下一张图中的 sys.path 中就会出现 基础 Pythonsite-packages 的对应路径)

    • 通过添加相关路径到 sys.path 中,Python 解释器可以找到需要的文件,编程时才能被顺利导入(import)。初始化启动时,通过自动导入 site 模块,为 sys.path 设置默认值。

      虚拟环境中 sys.path 内存入的路径

      (虚拟环境中的 Python 解释器可以访问以上所有路径查找文件,包括第二行创建该虚拟环境的 基础 Python 的标准库)

  • 修改 PYTHONPATH

    • 如上图所示,虚拟环境中 sys.path 最后两行的 site-packages 用虚拟环境另建的新路径替换了 基础 Pythonsite-packages 路径,所以实现了独立隔离项目依赖的效果。
  • 修改 Shell PATH 环境变量

    • 激活虚拟环境,其实是运行一个 Shell 脚本,把该环境对应的可执行文件路径放在系统环境变量 PATH 的最前方,于是会被操作系统优先调用。退出虚拟环境,则是运行 deactivate 脚本,使系统设置恢复如初。pyenv 的工作原理也是基于此,通过把它的 shims 路径放在 PATH 变量的最前面,来统一管理安装的各版本 Python 调用。
  • 使用绝对路径直接运行

    • 不是非要激活虚拟环境才能使用它,可以直接在 Shell 中输入虚拟环境 Python 的二进制文件所在的绝对路径运行使用它。(pyenv 也是将所有通过它安装的 Python 有规律地放在它所管理的路径下,可在系统任意路径下通过输入对应的 pyenv 命令调用需要的版本,当然也可以输入绝对路径调用。)
  • 个性化

    • 如果使用 Python 自带的 venv,当重建一个同名虚拟环境时,它并不会删除或完全覆盖原来那个同名虚拟环境已安装的外部依赖,再次重建时,需要在命令后面加上 --clear 才会删除原同名环境的数据

虚拟环境管理

  • 决定虚拟环境文件夹的存放位置

    使用 venv,可以把虚拟环境与项目源码统一放在一个路径下,也可以分开把所有的虚拟环境集中一起存放管理。使用 pyenv,默认是把所有的虚拟环境统一安装在 pyenv 管理的路径下,和对应项目的源码是分离存放的。除此之外,如果使用 IDE,如 VS Code 或 PyCharm,平台可能有它自己构建虚拟环境的路径与方法。

  • 虚拟环境应该是用完即弃的(disposable),除 pip 安装的依赖外,不要手动往里面添加其他东西,也不要把它纳入版本控制

  • 使用 requirements.txt 文件实现虚拟环境的可复现性

    #生成当前虚拟环境下已安装的所有依赖清单
    python -m pip freeze > requirements.txt
    
    #安装依赖清单中的所有软件
    python -m pip install -r requirements.txt
    

    可以把这个文件纳入版本控制,和项目源码一起 ship。但是仍然有漏洞,因为构建虚拟环境的 基础 Python 版本信息是缺失的,此外还可能出现因为 sub-dependency 不一致的问题导致的环境复现错误。解决方案有:

    • 使用 pip-tools 生成 requirements.txt

    • 使用 Pipenv 生成 Pipfile.lock

    • 使用 Poetry 生成 poetry.lock

    补充:以上 p 打头的第三方纯 Python 依赖管理工具都是为了更好地解决 pip 的不足,即不够 fully deterministic。除了此处说的 sub-dependency(依赖的依赖) 问题,以及最开始 啰嗦预警 提到的非纯 Python 包的依赖管理问题,还有就是,通过 python setup.py install 安装的依赖,pip 也管不了。

  • 不要将虚拟环境文件夹 push 到 GitHub 仓库,不要纳入 CI/CD 流水线,不要复制到部署服务器上。通常远程服务就是以隔离的虚拟环境形式提供的,只需要根据 requirements.txt 文件安装所有需要的依赖即可。

  • 其他相关工具

    • Poetry:Python 依赖管理与打包工具

    • Pipenv:依赖管理与打包;基于 virtualenv;相对较慢

    • pyenv:其实本身和虚拟环境无关,而是安装、管理不同版本的 Python,但是因为 pyenv-virtualenv 插件,经常也和虚拟环境联系在了一起


以上内容主要是基于 Python 官方自带的 venv 模块,此外常用的还有 conda 以及 virtualenvConda 功能更多,如不仅支持 Python,还支持其他编程语言。因为我自己用的是 pyenv 以及它基于 virtualenv 的插件 pyenv-virtualenv,所以此处不考虑 Conda。


virtualenv

Virtualenvvenv 的超集,而且它实际比 venv 更早出现。

相比 venv 的优势:

  • 创建虚拟环境的速度更快

  • 提供最新的 pipsetuptools,无需创建完成后再更新

  • 支持 Python 2.x

pyenv - Python 版本管理

主页

功能与特点

  • 支持各个用户(per-user)设置各自的系统全局 Python 版本

  • 支持为每个项目(per-project)提供各自对应的 Python 版本

  • 不依赖 Python 本身,.pyenv 是纯 Shell 脚本

工作原理

Shims

通过在 PATH 环境变量中加入 Shims 路径拦截所有 Python 包括 pip 命令,然后传递给 pyenv,进而匹配到各软件应用对应的 Python 安装版本。例如,如果在命令行中输入 pip:

  • 操作系统会去 PATH 中寻找叫作 pip 的可执行文件

  • 然后找到叫作 pip 的 pyenv shim(因为在 PATH 中排在最前面)

  • 于是系统运行叫作 pip 的 shim,实际则是把命令传递给了 pyenv

Python 版本选择

pyenv 选择某 Python 版本的顺序:

  • 当前工作 Shell 对应的版本,可由 pyenv shell 命令通过设定 PYENV_VERSION 环境变量设定

  • 某应用软件具体对应的 Python,由当前路径下的 .python-version 文件决定,可通过 pyenv local 命令覆盖该文件的设定

  • 通过搜索每一个 parent 路径直到整个文件系统的根路径,找到的第一个 .python-version 文件

  • 全局 $(pyenv root)/version 文件,这个文件的内容由命令 pyenv global 修改设定。如果不存在,pyenv 就会认定用户想要用的是 System Python

关于 系统 Python,即 PATH 中,Shims 之后能找到的任一 Python。无论是操作系统自带的 Python,还是通过其他软件包如 Homebrew 另外安装的不同版本的 Python,pyenv 对此不做识别区分,谁在 PATH 中排在前面就把谁当 系统 Python 调用。(所以,通过 which python 确认当前使用的版本还是很有必要的,以免误用。)

所有通过 pyenv 安装的 Python 都在 $(pyenv root)/versions 路径下各自对应的路径中。

具体到我本机,pyenv 默认的是安装在 home 的用户路径下,即 ~/.pyenv(=/home/user_name/.pyenv),其中就含有上面提到的 version 文件,里面写着 systempyenv系统 的理解前面刚已提及(未必真的是操作系统 Python)。除非位于对应虚拟环境的项目路径下,里面会含有 .python-version 文件,注明使用该虚拟环境对应安装的 Python,根据 pyenv 对 Python 版本的选择顺序,会优先被调用。

以上内容啰啰嗦嗦一大堆,其实就是:

  1. 如果在虚拟环境对应项目的路径下,如 BeatsTracking,则默认用它对应的 Python,但是也可以通过 pyenv local version_number 临时修改当前想要调用的版本,如此一来,Shell 中输入 python 调用的就是 local 临时指定的版本。在系统的其他位置,该 local 操作也是同理生效。

  2. pyenv global 全局默认使用的是 系统 Python,但是也可以通过该命令做不同的设置修改

  3. 在 Shell 中输入绝对路径调用某一版本 Python,如 ~/.pyenv/versions/3.8.7/bin/python~/.pyenv/versions/3.8.7/envs/BeatsTracking/bin/python

使用

首先,安装 Python 构建依赖,pyenv 建议的 Ubuntu/Debian/Mint 构建环境

sudo apt update; sudo apt install build-essential libssl-dev zlib1g-dev \
libbz2-dev libreadline-dev libsqlite3-dev curl \
libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev llvm
#列出所有可供安装的 Python 版本
pyenv install -l

#安装某版本的 Python
pyenv install python-version-number

#查看系统内当前所有的 Python 版本
pyenv versions

#卸载
pyenv uninstall python-version-name

#选定全局性偏向使用的版本
pyenv global python-version-name

#设定局部版本
pyenv local python-version-name

pyenv-virtualenv

pyenv-virtualenvpyenv 的一个插件,用来管理 virtualenvconda Python 环境。

#以指定的 Python 版本在 $(pyenv root)/versions 路径下新建 name_directory 虚拟环境
pyenv virtualenv python_version_name env_name

#查看已创建的虚拟环境,每个环境会有两个条目,其中一个只是 symlink
pyenv virtualenvs

#删除某个虚拟环境
pyenv uninstall env_name

综上,当为某一个新建的 Python 项目(new_project)创建虚拟环境时,可如下操作:

# 1.安装某一版本的 Python
pyenv install python_version_number

# 2.创建虚拟环境 new_project
pyenv virtualenv python_version_number new_project

# 3.去到新项目所在路径下
cd new_project

# 4.将虚拟环境 Python 设置为该路径(即新项目)默认使用的 Python
# 这个命令会在当前路径下生成一个 .python-version 文件
# 于是这个虚拟环境对应的 Python 二进制文件路径会被放在 $PATH 最前面优先被运行
pyenv local new_project

注:以上命令中,2、4 的 new_project 对应虚拟环境名称,3 中的 new_project 对应同名项目的路径名

Conda

Miniconda 安装与卸载

单独安装与卸载:

# 安装
mkdir -p ~/miniconda3
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda3/miniconda.sh
bash ~/miniconda3/miniconda.sh -b -u -p ~/miniconda3
rm -rf ~/miniconda3/miniconda.sh

# 卸载
rm -rf ~/miniconda3
rm -rf ~/.condarc ~/.conda

使用 pyenv 安装与卸载:

# 默认安装在 ~/.pyenv/versions/ 路径下
pyenv install miniconda3-latest

# 卸载
pyenv uninstall miniconda3-latest

修改 ~/.bashrc 文件,加入 $PATH 环境变量

# 独立安装
~/miniconda3/bin/conda init bash

# 使用pyenv安装
~/.pyenv/versions/miniconda3-latest/bin/conda init bash

更新 conda

conda update conda

创建 conda 虚拟环境同时安装依赖软件

在安装 conda 时同时也安装了一个 Python,这个最外围的 conda 自身所处的环境叫做 base environment。其中的 Python 也可以理解为 基础(base)Python,是供 conda 自己依赖使用的,不要动它。前面已讲过,Conda 创建的虚拟环境比较特殊,其中会安装一个独立完整的 Python 专供虚拟环境使用。后续安装依赖时千万小心不要混淆环境错用了 基础 Python

# 创建名为 base_py 的虚拟环境同时安装 jupyterlab
conda create -n base_py jupyterlab

# 如果不同时安装 jupyterlab 这些依赖,也可以:
pyenv virtualenv base_py

如上方命令所示,在创建名叫 base_py 的虚拟环境时,同时指定了安装依赖 jupyterlab,还可以在后面添加更多确定要用的软件,这种同时安装的方式相比逐个安装,能更好地回避依赖冲突问题。

注意:

在 conda 环境下不要使用 pip 安装依赖,万不得已非要用 pip 安装时,那就最后再用,而且先克隆一个相同的虚拟环境再用 pip 安装

激活虚拟环境

参考 pyenv-virtualenv 主页关于 Anaconda 和 Miniconda 部分说明,使用 pyenv 激活。每次手动输入命令过于麻烦,可以在 base_py 路径下执行 pyenv local miniconda3-latest/envs/base_py 生成 .python-version 文件,以后进入该路径会自动激活对应虚拟环境,不需要再做其他操作。(最后面 ## 1221 更新 以及 ## 1222 更新 都与此有关。)

# 激活虚拟环境 base_py
pyenv activate miniconda3-latest/envs/base_py

# 退出虚拟环境状态,回到 base 环境
pyenv deactivate

前面已讲过,激活就会修改 $PATH 环境变量,把这个版本的 Python 二进制文件路径放到最前方。

导出虚拟环境配置文件

# 导出虚拟环境 base_py 的环境配置文件
conda env export -n base_py > environment.yml

这个环境文件(environment.yml)中记录了 conda base_py 环境中所有已安装的软件及其具体版本,将它放入项目所在仓库,接受 git 版本控制管理。

参考


以下部分纯作记录留存用,无阅读必要。

1222 更新:通过 pyenv 安装的 conda,还是要归 pyenv

按照昨天最后的理解和操作,今天开机发现还是有问题。对 pyenvconda 的理解与使用仍需要校正。前面正文 Conda 使用部分 也已相应修改。因为想要留存记录,不想把 1221 更新 的部分删去,就全都移到了篇尾。

其实很多关键点 pyenv-virtualenv 的官方文档/使用说明从一开始都讲清楚了,但是没有办法,总是后来才缓过神来。

再次梳理一些要点:

  • pyenv 可以安装各种版本的 Python,其中包括 miniconda 这个生态下的

  • pyenv 通过插件 pyenv-virtualenv 创建管理 Python 虚拟环境,同上,既包括典型如从 virtualenv 顺延下来的依附于某个 基础 Python 的虚拟环境,同时也支持 conda 类的虚拟环境(这种环境中安装着完整版的 Python)

  • 综合以上两条,pyenv 联合 pyenv-virtualenv 就能安装管理系统内各种版本类型的 Python 及其对应虚拟环境

  • pyenvcondapyenv-virtualenv 都对 .bashrc 文件做了修改,于是每次启动命令行/终端的时候,Shell 自动就会对环境变量(如 $PATH )进行配置,完成一些初始化工作(如下图中的 pyenv init - 以及 pyenv-virtualenv init -

    pyenv 对 .bashrc 文件做的修改
    conda 对 .bashrc 文件做的修改
  • 通过 pyenv 安装的 conda 还是受它管理影响,最关键的,激活或关闭某 conda 虚拟环境,不再用 conda 命令,而是和 virtualenvvenv 类型的虚拟环境相似,都统一用 pyenv 命令:

    pyenv activate env_name
    pyenv deactivate
    

我这两天遇到的问题就跟最后一条相关,如果要启用我的 conda 虚拟环境 base_py,应该输入 pyenv activate miniconda3-latest/envs/base_py 命令。每次这样操作会比较麻烦,所以可以在我的 base_py 文件路径下,通过 pyenv local miniconda3-latest/envs/base_py 命令生成一个 .python-version 文件,如此一来,每当进入该路径后,自动就会激活该虚拟环境,不需要再额外用 condapyenv 做任何操作。此前,我对此还是有些误解,分析问题产生的原因时存在偏差。

参考:

1221 更新(此部分理解有误,仅作留存,见 1222 更新)

又出了一点问题,从上次搞定之后到昨天都正常,忘记这之间是否有重启过电脑,昨天晚上因为风扇响声太吵而关机,今天开机 conda activate base_py 后运行 Python,pyenv 报错该虚拟环境对应的 Python 未安装......

装是绝对装了,还用呢,肯定也没崩,我猜就是 pyenv 还有 conda$PATH 的管理问题。

下面正文已经详细到啰嗦地交待过,miniconda 我是通过 pyenv 安装的,所以无论是 conda 的基础 base 环境还是它又安装的虚拟环境 base_py,它们各自对应的 Python 全都归属在 pyenv 的路径下统一管理,即 versions 路径。

一般的,使用某 conda 虚拟环境及其对应 Python,只需要 conda activate env_name 即可,不放心的话就再 conda info -e 确认一下该虚拟环境确实被成功激活了(现在已养成强迫性确认的习惯,生怕用错环境和版本,主要是关系到后续新安装依赖的管理问题)。

因为我同时用 pyenv,所以此前对本地一个同名 base_py 路径做了 pyenv local miniconda3-latest/envs/base_py 处理,即指定该路径对应使用 base_py 这个虚拟环境对应的 Python,这个操作跟 conda 无关,是 pyenv 的功能,如此一来,这个路径下就会生成一个 .python-version 文件,里面注明当前路径下指定使用的 Python 版本,理论上每次进入到这个路径后,自动就会使用这个版本的 Python。

上面一段删除是说我现在觉得这种操作不好,会影响 conda 的路径管理,例如,在其他路径下 conda activate base_py 会失败报错,而原本虚拟环境的激活跟我处在哪个路径下是没有关系的,我可以在不同的路径下用这同一个环境工作,当然也可以固定在唯一一个路径下使用。

删除上面一段的操作,即不在本机 base_py 路径下生成 pyenv.python-version 文档,也是为了清晰分割 pyenvconda 的功能,即使可以也不混用。不管 pyenv 对于 Python 的版本有何简便管理功能,当前语境下我仅仅用它安装 miniconda,done。剩下的全都交给 conda,即安装虚拟环境及对应版本 Python;激活或关闭使用虚拟环境;安装、管理虚拟环境的所有依赖。

再以命令的形式明确一下:

# 安装(卸载直接用 uninstall 替换)
pyenv install miniconda3-latest
# conda 修改 .bashrc
~/.pyenv/versions/miniconda3-latest/bin/conda init bash
# conda 更新自己
conda update conda
# conda 安装新虚拟环境,同时安装项目需要的依赖,同时安相比一个个单独安能更好地回避冲突问题
conda create -n base_py jupyterlab
# 激活某虚拟环境,关闭只需 conda deactivate(不加环境名)
conda activate base_py
# 导出某虚拟环境已安装的所有依赖
conda env export -n base_py > environment.yml

一旦激活某虚拟环境,无论 cd 到哪个路径,按说都应是它。但保险起见,还是 pyenv versions 双重确认一下。

最开始那个问题后来用 pyenv local 再具体指定一下 Python 版本就好了,但是如前面所述,我已删除 .python-version 文件且不打算再让 pyenv 碰虚拟环境。

另外,过程中又看到一篇文章:Relieving your Python packaging pain,作者的观点也算是激进的,就是 pyenvconda 这种软件全都不要用,就是尽可能的 Python 纯原生。理解,也有启发,但还是有问题。正如我之前正文中已引用过的,Conda: Myths and Misconceptions 这篇文章已厘清过,conda 的产生是有原因的,就是有纯原生解决不了的问题,这里不再赘述,只是两篇文章贴在一起以备参考。