venv --- 创建虚拟环境

在 3.3 版新加入.

原始碼:Lib/venv/


venv 模組支援建立輕量級的「虛擬環境」,每個環境擁有獨立的 Python 套件組合,安裝在各自的 site 路徑底下。一個虛擬環境是以某個已安裝好的 Python 版本當作虛擬環境的「基底」Python,而且可以選擇是否和基底環境的套件隔離,如此一來,只有明確安裝在虛擬環境中的套件才能使用

当在一个虚拟环境内使用时,常见安装工具如 pip 将把 Python 软件包安装到虚拟环境中而无需显式地指明这一点。

虚拟环境是(主要的特性):

  • 用来包含支持一个项目(库或应用程序)所需的特定 Python 解释器、软件库和二进制文件。 它们在默认情况下与其他虚拟环境中的软件以及操作系统中安装的 Python 解释器和库保持隔离。

  • 包含在一个目录中,根据惯例被命名为项目目录下的``venv`` 或 .venv,或是有许多虚拟环境的容器目录下,如 ~/.virtualenvs

  • 不可签入 Git 等源代码控制系统。

  • 被视为是可丢弃性的 —— 应当能够简单地删除并从头开始重建。 你不应在虚拟环境中放置任何项目代码。

  • 不被视为是可移动或可复制的 —— 你只能在目标位置重建相同的环境。

更多關於 Python 虛擬環境的背景資訊請見 PEP 405

可用性: 非 Emscripten,非 WASI。

此模块在 WebAssembly 平台 wasm32-emscriptenwasm32-wasi 上不适用或不可用。 请参阅 WebAssembly 平台 了解详情。

建立虛擬環境

建立虛擬環境的方法是透過執行指令 venv

python -m venv /path/to/new/virtual/environment

執行此命令會建立目標目錄(同時也會建立任何還不存在的父目錄)並在目錄中放置一個名為 pyvenv.cfg 的檔案,其中包含一個指向執行該命令的 Python 安裝路徑的 home 鍵(目標目錄的常見名稱為 .venv)。同時,它會建立一個 bin (在 Windows 上為 Scripts)子目錄,其中包含一個 Python 二進位檔案的副本/符號連結(根據建立環境時使用的平台或引數而定)。此外,它還會建立一個(最初為空的) lib/pythonX.Y/site-packages 子目錄(在 Windows 上為 Lib\site-packages)。如果指定的目錄已存在,則將重新使用該目錄。

在 3.5 版的變更: 目前建議使用 venv 來建立虛擬環境。

在 3.6 版之後被棄用: pyvenv 是在 Python 3.3 和 3.4 中建立虛擬環境的推薦工具,但在 Python 3.6 中已被棄用

在 Windows 上,執行以下命令以使用 venv

c:\>c:\Python35\python -m venv c:\path\to\myenv

或者,如你已經為你的 Python 安裝配置了 PATHPATHEXT 變數,則可以執行以下命令:

c:\>python -m venv c:\path\to\myenv

如果使用 -h 選項執行該命令,將會顯示可用的選項:

usage: venv [-h] [--system-site-packages] [--symlinks | --copies] [--clear]
            [--upgrade] [--without-pip] [--prompt PROMPT] [--upgrade-deps]
            ENV_DIR [ENV_DIR ...]

Creates virtual Python environments in one or more target directories.

positional arguments:
  ENV_DIR               A directory to create the environment in.

optional arguments:
  -h, --help            show this help message and exit
  --system-site-packages
                        Give the virtual environment access to the system
                        site-packages dir.
  --symlinks            Try to use symlinks rather than copies, when symlinks
                        are not the default for the platform.
  --copies              Try to use copies rather than symlinks, even when
                        symlinks are the default for the platform.
  --clear               Delete the contents of the environment directory if it
                        already exists, before environment creation.
  --upgrade             Upgrade the environment directory to use this version
                        of Python, assuming Python has been upgraded in-place.
  --without-pip         Skips installing or upgrading pip in the virtual
                        environment (pip is bootstrapped by default)
  --prompt PROMPT       Provides an alternative prompt prefix for this
                        environment.
  --upgrade-deps        Upgrade core dependencies: pip setuptools to the
                        latest version in PyPI

Once an environment has been created, you may wish to activate it, e.g. by
sourcing an activate script in its bin directory.

在 3.9 版的變更: 新增 --upgrade-deps 選項以將 pip 和 setuptools 升級至 PyPI 上的最新版本

在 3.4 版的變更: 預設情況下安裝 pip,並新增了 --without-pip--copies 選項

在 3.4 版的變更: 在較早的版本中,如果目標目錄已存在,除非提供了 --clear--upgrade 選項,否則會引發錯誤。

備註

雖然在 Windows 上支援符號連結,但並不建議使用。特別需要注意的是,在檔案總管中按兩下 python.exe 會急切地解析符號連結並忽略虛擬環境。

備註

在 Microsoft Windows 上,可能需要通過設置使用者的執行策略來啟用 Activate.ps1 腳本。你可以發出以下 PowerShell 命令來執行此操作:

PS C:> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

有關更多資訊,請參閱關於執行策略

被建立的 pyvenv.cfg 檔案還包括了 include-system-site-packages 的鍵,如果使用 venv 執行時帶有 --system-site-packages 選項,則設置為 true,否則設置為 false

除非 --without-pip 選項被提供,否則將調用 ensurepip 來啟動 pip 到虛擬環境中。

可以向 venv 提供多個路徑,這樣每個提供的路徑都將根據給定的選項建立一個相同的虛擬環境。

虛擬環境如何運作

當 Python 直譯器跑在虛擬環境時,sys.prefixsys.exec_prefix 會指向虛擬環境的目錄,而 sys.base_prefixsys.base_exec_prefix 會指向建立虛擬環境的基礎 Python 的目錄。檢查 sys.prefix != sys.base_prefix 就可以確定目前的直譯器是否跑在虛擬環境中。

一个虚拟环境可以通过位于其二进制文件目录目录 (在 POSIX 上为 bin;在 Windows 上为 Scripts ) 中的脚本来“激活”。 这会将该目录添加到你的 PATH,这样运行 python 时就会发起调用虚拟环境的 Python 解释器,从而可以运行该目录中安装的脚本而不必使用其完整路径。 激活脚本的发起调用方式是平台专属的 (<venv> 必须用包含虚拟环境目录的路径来替换):

平台

Shell

用于激活虚拟环境的命令

POSIX

bash/zsh

$ source <venv>/bin/activate

fish

$ source <venv>/bin/activate.fish

csh/tcsh

$ source <venv>/bin/activate.csh

PowerShell

$ <venv>/bin/Activate.ps1

Windows

cmd.exe

C:\> <venv>\Scripts\activate.bat

PowerShell

PS C:\> <venv>\Scripts\Activate.ps1

在 3.4 版新加入: fishcsh 激活脚本。

在 3.8 版新加入: 在 POSIX 上安装 PowerShell 激活脚本,以支持 PowerShell Core。

激活一个虚拟环境的操作 不是必需的,因为你完全可以在发起调用 Python 时指明特定虚拟环境的 Python 解释器的完整路径。 更进一步地说,安装在虚拟环境中的所有脚本也都可以在不激活该虚拟环境的情况下运行。

为了达成此目的,安装到虚拟环境中的脚本将包含一个以“井号叹号”打头的行用来指定虚拟环境的 Python 解释器,例如 #!/<path-to-venv>/bin/python。 这意味着无论 PATH 的值是什么该脚本都将使用指定的解释器运行。 在 Windows 上对“井号叹号”行的处理将在你安装了 适用于Windows的Python启动器 的情况下获得支持。 这样,在 Windows 资源管理器窗口中双击一个已安装的脚本应当会使用正确的解释器运行它而无需激活相应虚拟环境或设置 PATH

当一个虚拟环境已被激活时,VIRTUAL_ENV 环境变量会被设为该虚拟环境的路径。 由于使用虚拟环境并不需要显式地激活它,因此 VIRTUAL_ENV 并不能被用来可靠地确定是否正在使用虚拟环境。

警告

因为安装在虚拟环境中的脚本不应要求必须激活该虚拟环境,所以它们的“井号叹号”行会包含虚拟环境的绝对路径。 因为这一点,所以虚拟环境在通常情况下都是不可移植的。 你应当保证提供重建一个虚拟环境的简便方式(举例来说,如果你准备了需求文件 requirements.txt,则可以使用虚拟环境的 pip 执行 pip install -r requirements.txt 来安装虚拟环境所需的所有软件包)。 如果出于某种原因你需要将虚拟环境移动到一个新的位置,则你应当在目标位置上重建它并删除旧位置上的虚拟环境。 如果出于某种原因你移动了一个虚拟环境的上级目录,你也应当在新位置上重建该虚拟环境。 否则,安装到该虚拟环境的软件包可能无法正常工作。

你可以通过在 shell 中输入 deactivate 来取消激活一个虚拟环境。 取消激活的实现机制依赖于具体平台并且属于内部实现细节(通常,将会使用一个脚本或者 shell 函数)。

API

上述提到的高階 method(方法)透過簡單的 API 使用, 為第三方虛擬環境建立者提供可以依據他們需求來建立環境的客製化機制: EnvBuilder class。

class venv.EnvBuilder(system_site_packages=False, clear=False, symlinks=False, upgrade=False, with_pip=False, prompt=None, upgrade_deps=False)

進行實例化時,class EnvBuilder 接受下列的關鍵字引數:

  • system_site_packages -- 為一個 Boolean (布林值),並表明系統的 Python site-packages 是否可以在環境中可用(預設為 False )。

  • clear -- 為一個 Boolean,如果為 true,則在建立環境之前,刪除目標目錄內所有存在的內容。

  • symlinks -- 為一個 Boolean,並表明是否嘗試與 Python 二進位檔案建立符號連結而不是複製該檔案。

  • upgrade -- 為一個 Boolean,若為 true,則會在執行 Python 時為現有的環境進行升級。目的是讓 Python 可以升級到位(預設為 False)。

  • with_pip -- 為一個 Boolean,若為 true,則確保 pip 有安裝至虛擬環境之中。當有 --default-pip 的選項時,會使用 ensurepip

  • prompt -- 為一個 String(字串),該字串會在虛擬環境啟動時被使用。(預設為 None,代表該環境的目錄名稱會被使用)倘若出現特殊字串 "." ,則當前目錄的 basename 會做為提示路徑使用。

  • upgrade_deps -- 更新基礎 venv 模組至 PyPI 的最新版本

在 3.4 版的變更: 新增 with_pip 參數

在 3.6 版的變更: 新增 prompt 參數

在 3.9 版的變更: 新增 upgrade_deps 參數

第三方虛擬環境工具的建立者可以自由地使用 EnvBuilder class 作為 base class(基底類別)使用.

回傳的 env-builder 為一個物件,且帶有一個 method create

create(env_dir)

透過指定將會容納虛擬環境的目標目錄來建立一個虛擬環境(絕對路徑或相對路徑到該目錄),也就是在該目錄中容納虛擬環境。create method 將會在指定的目錄下建立環境,或是觸發適當的例外。

EnvBuilder class 的 create method 會闡述可用的 Hooks 以客製化 subclass (子類別):

def create(self, env_dir):
    """
    Create a virtualized Python environment in a directory.
    env_dir is the target directory to create an environment in.
    """
    env_dir = os.path.abspath(env_dir)
    context = self.ensure_directories(env_dir)
    self.create_configuration(context)
    self.setup_python(context)
    self.setup_scripts(context)
    self.post_setup(context)

每個 methods ensure_directories()create_configuration()setup_python()setup_scripts()post_setup() 都可以被覆寫。

ensure_directories(env_dir)

建立還不存在的環境目錄及必要的子目錄,並回傳一個情境物件(context object)。這個情境物件只是一個屬性 (例如:路徑) 的所有者,可被其他 method 使用。如果 EnvBuilder 已被建立且帶有 clear=True 的引數,該環境目錄下的內容將被清空,以及所有必要的子目錄將被重新建立。

回傳的情境物件(context object)其型別會是 types.SimpleNamespace,並包含以下屬性:

  • env_dir - 虚拟环境的位置。 将被用作激活脚本中的 __VENV_DIR__ (参见 install_scripts())。

  • env_name - 虚拟环境的名称。 将被用作激活脚本中的 __VENV_NAME__ (参见 install_scripts())。

  • prompt - 激活脚本要使用的提示符。 将被用作激活脚本中的 __VENV_PROMPT__ (参见 install_scripts())。

  • executable - 虚拟环境所使用的下层 Python 可执行文件。 这会将基于另一个虚拟环境创建虚拟环境的情况也纳入考虑。

  • inc_path - 虚拟环境的 include 路径。

  • lib_path - 虚拟环境的 purelib 路径。

  • bin_path - 虚拟环境的 script 路径。

  • bin_name - 相对于虚拟环境位置的 script 路径名称。 用于激活脚本中的 __VENV_BIN_NAME__ (参见 install_scripts())。

  • env_exe - 虚拟环境中 Python 解释器的名称。 用于激活脚本中的 __VENV_PYTHON__ (参见 install_scripts())。

  • env_exec_cmd - Python 解释器的名称,会将文件系统重定向也纳入考虑。 这可被用于在虚拟环境中运行 Python。

在 3.11 版的變更: venv sysconfig installation scheme 被用于构造所创建目录的路径。

create_configuration(context)

在环境中创建 pyvenv.cfg 配置文件。

setup_python(context)

在环境中创建 Python 可执行文件的拷贝或符号链接。在 POSIX 系统上,如果给定了可执行文件 python3.x,将创建指向该可执行文件的 pythonpython3 符号链接,除非相同名称的文件已经存在。

setup_scripts(context)

将适用于平台的激活脚本安装到虚拟环境中。

upgrade_dependencies(context)

Upgrades the core venv dependency packages (currently pip and setuptools) in the environment. This is done by shelling out to the pip executable in the environment.

在 3.9 版新加入.

post_setup(context)

占位方法,可以在第三方实现中重写,用于在虚拟环境中预安装软件包,或是其他创建后要执行的步骤。

在 3.7.2 版的變更: Windows 现在为 python[w].exe 使用重定向脚本,而不是复制实际的二进制文件。仅在 3.7.2 中,除非运行的是源码树中的构建,否则 setup_python() 不会执行任何操作。

在 3.7.3 版的變更: Windows 将重定向脚本复制为 setup_python() 的一部分而非 setup_scripts()。在 3.7.2 中不是这种情况。使用符号链接时,将链接至原始可执行文件。

此外,EnvBuilder 提供了如下实用方法,可以从子类的 setup_scripts()post_setup() 调用,用来将自定义脚本安装到虚拟环境中。

install_scripts(context, path)

path 是一个目录的路径,该目录应包含子目录 "common", "posix", "nt",每个子目录存有发往对应环境中 bin 目录的脚本。在下列占位符替换完毕后,将复制 "common" 的内容和与 os.name 对应的子目录:

  • __VENV_DIR__ 会被替换为环境目录的绝对路径。

  • __VENV_NAME__ 会被替换为环境名称(环境目录的最后一个字段)。

  • __VENV_PROMPT__ 会被替换为提示符(用括号括起来的环境名称紧跟着一个空格)。

  • __VENV_BIN_NAME__ 会被替换为 bin 目录的名称( binScripts )。

  • __VENV_PYTHON__ 会被替换为环境可执行文件的绝对路径。

允许目录已存在(用于升级现有环境时)。

有一个方便实用的模块级别的函数:

venv.create(env_dir, system_site_packages=False, clear=False, symlinks=False, with_pip=False, prompt=None, upgrade_deps=False)

通过关键词参数来创建一个 EnvBuilder,并且使用 env_dir 参数来调用它的 create() 方法。

在 3.3 版新加入.

在 3.4 版的變更: 新增 with_pip 參數

在 3.6 版的變更: 新增 prompt 參數

在 3.9 版的變更: 新增 upgrade_deps 參數

一个扩展 EnvBuilder 的例子

下面的脚本展示了如何通过实现一个子类来扩展 EnvBuilder。这个子类会安装 setuptools 和 pip 到被创建的虚拟环境中。

import os
import os.path
from subprocess import Popen, PIPE
import sys
from threading import Thread
from urllib.parse import urlparse
from urllib.request import urlretrieve
import venv

class ExtendedEnvBuilder(venv.EnvBuilder):
    """
    This builder installs setuptools and pip so that you can pip or
    easy_install other packages into the created virtual environment.

    :param nodist: If true, setuptools and pip are not installed into the
                   created virtual environment.
    :param nopip: If true, pip is not installed into the created
                  virtual environment.
    :param progress: If setuptools or pip are installed, the progress of the
                     installation can be monitored by passing a progress
                     callable. If specified, it is called with two
                     arguments: a string indicating some progress, and a
                     context indicating where the string is coming from.
                     The context argument can have one of three values:
                     'main', indicating that it is called from virtualize()
                     itself, and 'stdout' and 'stderr', which are obtained
                     by reading lines from the output streams of a subprocess
                     which is used to install the app.

                     If a callable is not specified, default progress
                     information is output to sys.stderr.
    """

    def __init__(self, *args, **kwargs):
        self.nodist = kwargs.pop('nodist', False)
        self.nopip = kwargs.pop('nopip', False)
        self.progress = kwargs.pop('progress', None)
        self.verbose = kwargs.pop('verbose', False)
        super().__init__(*args, **kwargs)

    def post_setup(self, context):
        """
        Set up any packages which need to be pre-installed into the
        virtual environment being created.

        :param context: The information for the virtual environment
                        creation request being processed.
        """
        os.environ['VIRTUAL_ENV'] = context.env_dir
        if not self.nodist:
            self.install_setuptools(context)
        # Can't install pip without setuptools
        if not self.nopip and not self.nodist:
            self.install_pip(context)

    def reader(self, stream, context):
        """
        Read lines from a subprocess' output stream and either pass to a progress
        callable (if specified) or write progress information to sys.stderr.
        """
        progress = self.progress
        while True:
            s = stream.readline()
            if not s:
                break
            if progress is not None:
                progress(s, context)
            else:
                if not self.verbose:
                    sys.stderr.write('.')
                else:
                    sys.stderr.write(s.decode('utf-8'))
                sys.stderr.flush()
        stream.close()

    def install_script(self, context, name, url):
        _, _, path, _, _, _ = urlparse(url)
        fn = os.path.split(path)[-1]
        binpath = context.bin_path
        distpath = os.path.join(binpath, fn)
        # Download script into the virtual environment's binaries folder
        urlretrieve(url, distpath)
        progress = self.progress
        if self.verbose:
            term = '\n'
        else:
            term = ''
        if progress is not None:
            progress('Installing %s ...%s' % (name, term), 'main')
        else:
            sys.stderr.write('Installing %s ...%s' % (name, term))
            sys.stderr.flush()
        # Install in the virtual environment
        args = [context.env_exe, fn]
        p = Popen(args, stdout=PIPE, stderr=PIPE, cwd=binpath)
        t1 = Thread(target=self.reader, args=(p.stdout, 'stdout'))
        t1.start()
        t2 = Thread(target=self.reader, args=(p.stderr, 'stderr'))
        t2.start()
        p.wait()
        t1.join()
        t2.join()
        if progress is not None:
            progress('done.', 'main')
        else:
            sys.stderr.write('done.\n')
        # Clean up - no longer needed
        os.unlink(distpath)

    def install_setuptools(self, context):
        """
        Install setuptools in the virtual environment.

        :param context: The information for the virtual environment
                        creation request being processed.
        """
        url = 'https://bitbucket.org/pypa/setuptools/downloads/ez_setup.py'
        self.install_script(context, 'setuptools', url)
        # clear up the setuptools archive which gets downloaded
        pred = lambda o: o.startswith('setuptools-') and o.endswith('.tar.gz')
        files = filter(pred, os.listdir(context.bin_path))
        for f in files:
            f = os.path.join(context.bin_path, f)
            os.unlink(f)

    def install_pip(self, context):
        """
        Install pip in the virtual environment.

        :param context: The information for the virtual environment
                        creation request being processed.
        """
        url = 'https://bootstrap.pypa.io/get-pip.py'
        self.install_script(context, 'pip', url)

def main(args=None):
    compatible = True
    if sys.version_info < (3, 3):
        compatible = False
    elif not hasattr(sys, 'base_prefix'):
        compatible = False
    if not compatible:
        raise ValueError('This script is only for use with '
                         'Python 3.3 or later')
    else:
        import argparse

        parser = argparse.ArgumentParser(prog=__name__,
                                         description='Creates virtual Python '
                                                     'environments in one or '
                                                     'more target '
                                                     'directories.')
        parser.add_argument('dirs', metavar='ENV_DIR', nargs='+',
                            help='A directory in which to create the '
                                 'virtual environment.')
        parser.add_argument('--no-setuptools', default=False,
                            action='store_true', dest='nodist',
                            help="Don't install setuptools or pip in the "
                                 "virtual environment.")
        parser.add_argument('--no-pip', default=False,
                            action='store_true', dest='nopip',
                            help="Don't install pip in the virtual "
                                 "environment.")
        parser.add_argument('--system-site-packages', default=False,
                            action='store_true', dest='system_site',
                            help='Give the virtual environment access to the '
                                 'system site-packages dir.')
        if os.name == 'nt':
            use_symlinks = False
        else:
            use_symlinks = True
        parser.add_argument('--symlinks', default=use_symlinks,
                            action='store_true', dest='symlinks',
                            help='Try to use symlinks rather than copies, '
                                 'when symlinks are not the default for '
                                 'the platform.')
        parser.add_argument('--clear', default=False, action='store_true',
                            dest='clear', help='Delete the contents of the '
                                               'virtual environment '
                                               'directory if it already '
                                               'exists, before virtual '
                                               'environment creation.')
        parser.add_argument('--upgrade', default=False, action='store_true',
                            dest='upgrade', help='Upgrade the virtual '
                                                 'environment directory to '
                                                 'use this version of '
                                                 'Python, assuming Python '
                                                 'has been upgraded '
                                                 'in-place.')
        parser.add_argument('--verbose', default=False, action='store_true',
                            dest='verbose', help='Display the output '
                                               'from the scripts which '
                                               'install setuptools and pip.')
        options = parser.parse_args(args)
        if options.upgrade and options.clear:
            raise ValueError('you cannot supply --upgrade and --clear together.')
        builder = ExtendedEnvBuilder(system_site_packages=options.system_site,
                                       clear=options.clear,
                                       symlinks=options.symlinks,
                                       upgrade=options.upgrade,
                                       nodist=options.nodist,
                                       nopip=options.nopip,
                                       verbose=options.verbose)
        for d in options.dirs:
            builder.create(d)

if __name__ == '__main__':
    rc = 1
    try:
        main()
        rc = 0
    except Exception as e:
        print('Error: %s' % e, file=sys.stderr)
    sys.exit(rc)

这个脚本同样可以 在线下载