如何使 Python 版本的可执行文件在多个 pyenv-virtualenv 虚拟环境中全局化

How to make Python version executables global across multiple pyenv-virtualenv virtual environments

A pyenv Python 版本(例如 3.10.4)具有与其关联的“正常”预期 Python 可执行文件(例如,pip2to3, pydoc)

$ ls "${PYENV_ROOT}/versions/3.10.4/bin"
2to3       idle   idle3.10  pip3     pydoc   pydoc3.10  python-config  python3-config  python3.10-config
2to3-3.10  idle3  pip       pip3.10  pydoc3  python python3        python3.10      python3.10-gdb.py

和一个 pyenv-virtualenv 虚拟环境只有在虚拟环境目录结构中可以获得的可执行文件

$ pyenv virtualenv 3.10.4 venv
$ ls "${PYENV_ROOT}/versions/venv"
bin  include  lib  lib64  pyvenv.cfg
$ ls "${PYENV_ROOT}/versions/venv/bin/"
Activate.ps1  activate  activate.csh  activate.fish  pip  pip3  pip3.10  pydoc  python  python3  python3.10

默认情况下,venv 虚拟环境在创建后不知道它所关联的 Python 版本的可执行文件,例如 2to3

$ pyenv activate venv
(venv) $ 2to3 --help
pyenv: 2to3: command not found

The `2to3' command exists in these Python versions:
  3.10.4

Note: See 'pyenv help global' for tips on allowing both
      python2 and python3 to be found.

因此,为了允许像 venv 这样的虚拟环境访问这些可执行文件,您将它和创建它的 Python 添加到 pyenv global 以便 pyenv"fall back" 到 Python 找不到可执行文件时的版本

(venv) $ pyenv deactivate
$ pyenv global venv 3.10.4
(venv) $ pyenv global 
venv
3.10.4
(venv) $ 2to3 --help | head -n 3
Usage: 2to3 [options] file|dir ...

Options:

此模式适用于一个虚拟环境,但是当您有多个虚拟环境时,如何保持对 2to3(或 pipx 等可执行文件的访问,如下所示`)?

(venv) $ pyenv virtualenv 3.10.4 example && pyenv activate example
(example) $ 2to3  
pyenv: 2to3: command not found

The `2to3' command exists in these Python versions:
  3.10.4

Note: See 'pyenv help global' for tips on allowing both
      python2 and python3 to be found.

可重现的例子

使用以下 Dockerfile

FROM debian:bullseye

SHELL ["/bin/bash", "-c"]
USER root

RUN apt-get update -y && \
    apt-get install --no-install-recommends -y \
        make \
        build-essential \
        libssl-dev \
        zlib1g-dev \
        libbz2-dev \
        libreadline-dev \
        libsqlite3-dev \
        wget \
        curl \
        llvm \
        libncurses5-dev \
        xz-utils \
        tk-dev \
        libxml2-dev \
        libxmlsec1-dev \
        libffi-dev \
        liblzma-dev \
        g++ && \
    apt-get install -y \
        git && \
    apt-get -y clean && \
    apt-get -y autoremove && \
    rm -rf /var/lib/apt/lists/*

# Install pyenv and pyenv-virtualenv
ENV PYENV_RELEASE_VERSION=2.3.0
RUN git clone --depth 1 https://github.com/pyenv/pyenv.git \
        --branch "v${PYENV_RELEASE_VERSION}" \
        --single-branch \
        ~/.pyenv && \
    pushd ~/.pyenv && \
    src/configure && \
    make -C src && \
    echo 'export PYENV_ROOT="${HOME}/.pyenv"' >> ~/.bashrc && \
    echo 'export PATH="${PYENV_ROOT}/bin:${PATH}"' >> ~/.bashrc && \
    echo 'eval "$(pyenv init -)"' >> ~/.bashrc && \
    . ~/.bashrc && \
    git clone --depth 1 https://github.com/pyenv/pyenv-virtualenv.git $(pyenv root)/plugins/pyenv-virtualenv && \
    echo 'eval "$(pyenv virtualenv-init -)"' >> ~/.bashrc

# Install CPython
ENV PYTHON_VERSION=3.10.4
RUN . ~/.bashrc && \
    echo "Install Python ${PYTHON_VERSION}" && \
    PYTHON_MAKE_OPTS="-j8" pyenv install "${PYTHON_VERSION}"

# Make 'base' virtual envirionment, add it and its Python version to global for
# executables like 2to3 or pipx to be findable
# c.f. https://github.com/pyenv/pyenv-virtualenv/issues/16#issuecomment-37640961
# and then install pipx into the 'base' virtual environment and use pipx to install
# pepotron
RUN . ~/.bashrc && \
    pyenv virtualenv "${PYTHON_VERSION}" base && \
    echo "" && echo "Python ${PYTHON_VERSION} has additional executables..." && \
    ls -lh "${PYENV_ROOT}/versions/${PYTHON_VERSION}/bin" && \
    echo "" && echo "...compared to 'base' virtualenv made with Python ${PYTHON_VERSION}" && \
    ls -lh "${PYENV_ROOT}/versions/base/bin" && \
    echo "" && echo "...because 'base' is actually a symlink" && \
    ls -lh "${PYENV_ROOT}/versions/" && \
    pyenv global base "${PYTHON_VERSION}" && \
    python -m pip --quiet install --upgrade pip setuptools wheel && \
    python -m pip --quiet install pipx && \
    python -m pipx ensurepath && \
    eval "$(register-python-argcomplete pipx)" && \
    pipx install pepotron

WORKDIR /home/data

构建于

docker build . --file Dockerfile --tag pyenv/multiple-virtualenvs:debug

可以运行用下面的来演示问题

$ docker run --rm -ti pyenv/multiple-virtualenvs:debug
(base) root@26dfa530cd82:/home/data# pyenv global 
base
3.10.4
(base) root@26dfa530cd82:/home/data# 2to3 --help | head -n 3
Usage: 2to3 [options] file|dir ...

Options:
(base) root@26dfa530cd82:/home/data# pipx list
venvs are in /root/.local/pipx/venvs
apps are exposed on your $PATH at /root/.local/bin
   package pepotron 0.6.0, installed using Python 3.10.4
    - bpo
    - pep
(base) root@26dfa530cd82:/home/data# pep 3.11
https://peps.python.org/pep-0664/
(base) root@26dfa530cd82:/home/data# pyenv virtualenv 3.10.4 example                          
(base) root@26dfa530cd82:/home/data# pyenv activate example
pyenv-virtualenv: prompt changing will be removed from future release. configure `export PYENV_VIRTUALENV_DISABLE_PROMPT=1' to simulate the behavior.
(example) root@26dfa530cd82:/home/data# 2to3
pyenv: 2to3: command not found

The `2to3' command exists in these Python versions:
  3.10.4

Note: See 'pyenv help global' for tips on allowing both
      python2 and python3 to be found.
(example) root@26dfa530cd82:/home/data# pipx
pyenv: pipx: command not found

The `pipx' command exists in these Python versions:
  3.10.4/envs/base
  base

Note: See 'pyenv help global' for tips on allowing both
      python2 and python3 to be found.

那么,如果安装在 pyenv-virtualenv 虚拟环境中,那么像 pipx 这样设计用于全局安装的东西怎么能在全球范围内工作,这样您就不必拥有在系统 Python?

中安装了 pip 的任何东西

似乎您不需要使用 pyenv activate 来激活虚拟环境,而是需要停用任何虚拟环境,然后仅使用 pyenv global <virtual environment name> <virtual environment Python version> 来有效地切换环境。我假设这不是在虚拟环境中使用 Python 版本可执行文件的唯一方法,因为这似乎会消除为pyenv-virtualenv.

你可以直接在pyenv前缀中执行pipx二进制文件,它应该可以正常工作。

Pyenv 的 shims 机制并不是真正为这样的全局二进制文件设计的。我天真地期望当本地环境没有安装二进制文件时,全局环境会作为回退,但我认为 pyenv 在回退到 $PATH.

之前只查看系统 Python

因此,如果您不将 pipx 安装到 system(如果您没有安装系统 pip,我怀疑您正在安装),那么天真的回退不起作用。 另一种方法是 运行 pyenv 使用临时环境,即 PYENV_VERSION=my-pipx-env pyenv exec pipx.

我想让它成为一个可执行文件,所以我建议将类似这样的东西添加到优先于 pyenv 路径的特殊 PATH 目录中:

#!/usr/bin/env bash
set -eu

export PYENV_VERSION="pipx"
exec "${PYENV_ROOT}/libexec/pyenv" exec pipx "$@"

不过,我很想放弃整个激活逻辑,直接 exec 来自环境 /binpipx 二进制文件,以避免 运行 进入任何 shell 配置错误。