解密python虚拟环境是如何创建的

本文深入探讨了Python虚拟环境的创建过程,以virtualenv为例,详细解析了如何通过修改PATH环境变量和命令行提示符实现环境隔离。进入虚拟环境后,`activate`脚本调整了系统路径,使得python命令指向虚拟环境的解释器,同时也修改了命令行提示符,显示虚拟环境名称。退出环境时,`deactivate`函数恢复原有环境设置。了解这一机制有助于更好地管理和维护Python项目。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

python虚拟环境,允许你在同一台机器上部署不同版本的python项目,即便是同一个python版本的项目,其依赖的第三方库也可以不同。在python2.7时代,实现虚拟环境最流行的库是virtualenv, 进入到python3时代后,内置了venv来支持虚拟环境的创建,此外,conda也可以非常方便的管理虚拟环境。

那么,你是否好奇过,虚拟环境是如何实现的,不同的环境之间究竟是如何做到隔离的,进入虚拟环境后,在shell命令行的最前端,还是显示虚拟环境的名称,这又是怎么实现的?

带着这些疑问,本文以virtualenv为例,向你解密python虚拟环境是如何创建的。

1. virtualenv

首先来安装virtualenv

pip install virtualenv

第二步,创建一个文件夹

mkdir test_env
cd test_env

第三步,创建虚拟环境

virtualenv  cool

此时,在test_env目录下,会生成一个名为cool的目录,其目录结构为

cool/
├── bin
│   ├── activate
│   ├── activate.csh
│   ├── activate.fish
│   ├── activate.ps1
│   ├── activate_this.py
│   ├── activate.xsh
│   ├── easy_install
│   ├── easy_install3
│   ├── easy_install-3.7
│   ├── easy_install3.7
│   ├── pip
│   ├── pip3
│   ├── pip-3.7
│   ├── pip3.7
│   ├── python 
│   ├── python3 -> python
│   ├── python3.7 -> python
│   ├── wheel
│   ├── wheel3
│   ├── wheel-3.7 
│   └── wheel3.7
├── lib
│   └── python3.7
│       └── site-packages
└── pyvenv.cfg

从目录结构来看,所谓的虚拟环境,就是一个完整的python安装环境,lib/python3.7/site-packages用来安装第三方依赖。

想要使用这个虚拟环境,还需要正式进入其中,第四步,进入虚拟环境

[root@bffd06f7a14d test_env]# source cool/bin/activate
(cool) [root@bffd06f7a14d test_env]#

执行此命令后,正式进入虚拟环境,shell的命令行提示符也发生的变化,进入虚拟环境以后,使用pip安装的第三方模块,都会被安装在cool/lib/python3.7/site-packages中,使用python命令,启动的python解释器是cool/bin/python,这一点是如何做到的,只要能破解这个难题,也就理解了python虚拟环境。

2. activate

2.1 修改PATH环境变量

python虚拟环境的关键点是bin目录下的activate文件,打开这个文件,内容为

# This file must be used with "source bin/activate" *from bash*
# you cannot run it directly


if [ "${BASH_SOURCE-}" = "$0" ]; then
    echo "You must source this script: \$ source $0" >&2
    exit 33
fi

deactivate () {
    unset -f pydoc >/dev/null 2>&1 || true

    # reset old environment variables
    # ! [ -z ${VAR+_} ] returns true if VAR is declared at all
    if ! [ -z "${_OLD_VIRTUAL_PATH:+_}" ] ; then
        PATH="$_OLD_VIRTUAL_PATH"
        export PATH
        unset _OLD_VIRTUAL_PATH
    fi
    if ! [ -z "${_OLD_VIRTUAL_PYTHONHOME+_}" ] ; then
        PYTHONHOME="$_OLD_VIRTUAL_PYTHONHOME"
        export PYTHONHOME
        unset _OLD_VIRTUAL_PYTHONHOME
    fi

    # The hash command must be called to get it to forget past
    # commands. Without forgetting past commands the $PATH changes
    # we made may not be respected
    hash -r 2>/dev/null

    if ! [ -z "${_OLD_VIRTUAL_PS1+_}" ] ; then
        PS1="$_OLD_VIRTUAL_PS1"
        export PS1
        unset _OLD_VIRTUAL_PS1
    fi

    unset VIRTUAL_ENV
    if [ ! "${1-}" = "nondestructive" ] ; then
    # Self destruct!
        unset -f deactivate
    fi
}

# unset irrelevant variables
deactivate nondestructive

VIRTUAL_ENV='/home/test_env/cool'
if ([ "$OSTYPE" = "cygwin" ] || [ "$OSTYPE" = "msys" ]) && $(command -v cygpath &> /dev/null) ; then
    VIRTUAL_ENV=$(cygpath -u "$VIRTUAL_ENV")
fi
export VIRTUAL_ENV

_OLD_VIRTUAL_PATH="$PATH"
PATH="$VIRTUAL_ENV/bin:$PATH"    # 修改PATH环境变量
export PATH

# unset PYTHONHOME if set
if ! [ -z "${PYTHONHOME+_}" ] ; then
    _OLD_VIRTUAL_PYTHONHOME="$PYTHONHOME"
    unset PYTHONHOME
fi

if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT-}" ] ; then
    _OLD_VIRTUAL_PS1="${PS1-}"
    if [ "x" != x ] ; then
        PS1="${PS1-}"
    else
        PS1="(`basename \"$VIRTUAL_ENV\"`) ${PS1-}"
    fi
    export PS1
fi

# Make sure to unalias pydoc if it's already there
alias pydoc 2>/dev/null >/dev/null && unalias pydoc || true

pydoc () {
    python -m pydoc "$@"
}

# The hash command must be called to get it to forget past
# commands. Without forgetting past commands the $PATH changes
# we made may not be respected
hash -r 2>/dev/null

这段shell代码,最核心的部分是

VIRTUAL_ENV='/home/test_env/cool'
if ([ "$OSTYPE" = "cygwin" ] || [ "$OSTYPE" = "msys" ]) && $(command -v cygpath &> /dev/null) ; then
    VIRTUAL_ENV=$(cygpath -u "$VIRTUAL_ENV")
fi
export VIRTUAL_ENV

_OLD_VIRTUAL_PATH="$PATH"
PATH="$VIRTUAL_ENV/bin:$PATH"    # 修改PATH环境变量
export PATH

使用virtualenv创建虚拟环境时,生成了activate文件,VIRTUAL_ENV 的值是在文件生成时确定下来的,这段代码的目的是修改PATH环境变量。我们都知道,当我们在命令行里使用python命令时,系统会从PATH环境变量里所设置的路径里查找python命令,而上面这段代码将 /home/test_env/cool/bin 放在PATH环境变量的最前面,这样,找到的python命令就是/home/test_env/cool/bin/python,找到的pip也正是/home/test_env/cool/bin/pip, 那么安装的第三方库也自然被安装在cool/lib/python3.7/site-packages中。

2.2 修改命令行提示符

修改命令行提示符的代码如下

if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT-}" ] ; then
    _OLD_VIRTUAL_PS1="${PS1-}"
    if [ "x" != x ] ; then
        PS1="${PS1-}"
    else
        PS1="(`basename \"$VIRTUAL_ENV\"`) ${PS1-}"
    fi
    export PS1
fi

这里的核心是PS1,它是一个环境变量,用来控制提示符的显示格式和内容,在shell里可以直接输出这个环境变量的值

[root@bffd06f7a14d test_env]# echo $PS1
[\u@\h \W]\$
[root@bffd06f7a14d test_env]#

PS1的常用参数以及含义:

  • \d :代表日期,格式为weekday month date,例如:“Mon Aug 1”
  • \H :完整的主机名称
  • \h :仅取主机名中的第一个名字
  • \t :显示时间为24小时格式,如:HH:MM:SS
  • \T :显示时间为12小时格式
  • \A :显示时间为24小时格式:HH:MM
  • \u :当前用户的账号名称
  • \v :BASH的版本信息
  • \w :完整的工作目录名称
  • \W :利用basename取得工作目录名称,只显示最后一个目录名
  • \# :下达的第几个命令
  • $ :提示字符,如果是root用户,提示符为 # ,普通用户则为 $

通过修改PS1的值,就可以修改提示符,比如我可以这样来操作

[root@bffd06f7a14d test_env]# PS1='(cool)[\h@\u \W \t]\$'
(cool)[bffd06f7a14d@root test_env 09:21:53]#

我对调了用户名和主机名的位置并增加了时间,在activate文件中,basename “$VIRTUAL_ENV” 其实就是执行basename /home/test_env/cool, 得到的结果是cool,经过字符串拼装,就修改了原来的提示符,在原有的提示符前面增加了(cool)

2.3 退出虚拟环境

退出虚拟环境,使用deactivate 命令,这个命令在activate文件中是一个函数实现的,其主要的任务是恢复之前的PS1,PYTHONHOME, PATH,这些环境变量在执行source activate命令时都保存了一份旧的。恢复以后,标志进入虚拟环境的提示符前缀也就没有了。

3. 总结

  1. 所谓虚拟环境,本质上就是一个python标准环境
  2. 进入虚拟环境,需要使用source命令,activate里的环境变量都会生效,修改PATH环境变量,修改命令行提示符,增加虚拟环境前缀,同时注册了deactivate函数
  3. 退出虚拟环境,正是调用之前注册的deactivate函数
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

酷python

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值