# 28.3. `venv` — 虚拟环境的创建

版本3.3中的新功能。

源代码： Lib / venv /

`venv` 模块与它们自己的网站目录，选择分离系统站点目录，为创建轻量级的"虚拟环境"提供支持。每个虚拟环境都有他自己的python二进制文件（允许在里面创建另一个版本的python），同时有自己独立的开发环境，可以在里面安装各种只在这个环境里面使用的包

有关Python虚拟环境的更多信息，请参见 PEP 405。

## 28.3.1. Creating virtual environments

创建 虚拟环境 是通过执行 `pyvenv` 脚本︰

    pyvenv /path/to/new/virtual/environment

运行此命令将创建目标目录 （创建任何不存在的父目录） 和 `pyvenv.cfg` 文件置于具有指向 Python 安装在运行命令从 `首页` 键。它还创建一个 `bin` （或 Windows `脚本`） 子目录包含 `python` 的二进制文件 （或在 Windows 的情况下的二进制文件） 的副本。它还创建 （最初为空） `lib/pythonX.Y/site-packages` 子目录 （在 Windows 上，这是 `Lib\site 软件包`）。

也可以看看

Python包装用户指南：创建和使用虚拟环境

在Windows上，如果没有相关的PATH和PATHEXT设置，则可能需要调用`pyvenv`脚本：

    c:\Temp>c:\Python35\python c:\Python35\Tools\Scripts\pyvenv.py myenv

或等效地：

    c:\Temp>c:\Python35\python -m venv myenv

该命令如果以`-h`运行，将显示可用的选项：

    usage: venv [-h] [--system-site-packages] [--symlinks | --copies] [--clear]
                [--upgrade] [--without-pip]
                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)

根据如何调用`venv`功能，使用消息可以稍微改变，例如，引用`pyvenv`而不是`venv`。

在版本3.4中更改：默认安装pip，添加了`--without-pip`和`--copies`选项

在版本3.4中已更改：在早期版本中，如果目标目录已存在，则会出现错误，除非`--clear`或`--upgrade` 现在，如果指定了现有目录，则删除其内容，并且处理该目录，就好像它是新创建的。

The created `pyvenv.cfg` file also includes the `include-system-site-packages` key, set to `true` if `venv` is run with the `--system-site-packages` option, `false` otherwise.

除非给出`--without-pip`选项，否则将调用`ensurepip`以将`pip`引导到虚拟环境中。

多个路径可以给予`pyvenv`，在这种情况下，将在每个提供的路径根据给定的选项创建一个相同的virtualenv。

一旦创建了venv，它可以使用venv的二进制目录中的脚本“激活”。脚本的调用是平台特定的：

平台 | Shell | 命令激活虚拟环境
--- | --- | ---
Posix | bash / zsh | $ source / bin / activate
| fish | $。/bin/activate.fish
| csh / tcsh | $ source /bin/activate.csh
Windows | cmd.exe | C：\> \ Scripts \ activate.bat
| PowerShell | PS C：\> \ Scripts \ Activate.ps1

您不具体需要来激活环境；激活只是将venv的二进制目录添加到你的路径，所以“python”调用venv的Python解释器，你可以运行安装的脚本，而不必使用他们的完整路径。但是，安装在venv中的所有脚本都应该可以在不激活的情况下运行，并且可以自动运行venv的Python。

您可以通过在shell中键入“deactivate”来停用venv。确切的机制是特定于平台的：例如，Bash激活脚本定义了一个“deactivate”函数，而在Windows上有称为`deactivate.bat`和`Deactivate.ps1`

新版本3.4： `fish`和`csh`激活脚本。

注意

虚拟环境（也称为`venv`）是一个Python环境，使得安装在其中的Python解释器，库和脚本与安装在其他虚拟环境中的Python解释器，库和脚本隔离，安装在一个“系统”Python，即其中一个作为操作系统的一部分安装。

venv是一个目录树，它包含Python可执行文件和其他文件，这些文件指示它是一个venv。

常用的安装工具，例如`Setuptools`和`pip`，可以正常工作当venv处于活动状态时，他们将Python软件包安装到venv中，而不需要明确地通知。

当活动时（即 the venv’s Python interpreter is running), the attributes `sys.prefix` and `sys.exec_prefix` point to the base directory of the venv, whereas `sys.base_prefix` and `sys.base_exec_prefix` point to the non-venv Python installation which was used to create the venv. 如果venv不活动，则`sys.prefix`与`sys.base_prefix`相同，`sys.exec_prefix`与`sys.base_exec_prefix`（他们都指向一个非venv Python安装）。

当venv处于活动状态时，将更改所有distutils配置文件的任何更改安装路径的选项都将被忽略，以防止项目无意中安装在虚拟环境之外。

When working in a command shell, users can make a venv active by running an `activate` script in the venv’s executables directory (the precise filename is shell-dependent), which prepends the venv’s directory for executables to the `PATH` environment variable for the running shell. 在其他情况下应该没有必要激活venv - 脚本安装到venvs有一个shebang线指向venv的Python解释器。这意味着脚本将与该解释器一起运行，而不管`PATH`的值。在Windows上，如果您安装了适用于Windows的Python Launcher（在3.3中已添加到Python中，请参阅 PEP 397以获取更多详细信息），才支持shebang行处理。因此，在Windows资源管理器窗口中双击已安装的脚本应该使用正确的解释器运行脚本，而不需要在`PATH`中对其venv进行任何引用。

## 28.3.2. API

上面描述的高级方法使用简单的API，其为第三方虚拟环境创建者提供用于根据他们的需要定制环境创建的机制，`EnvBuilder`类。

class `venv.``EnvBuilder`(system_site_packages=False, clear=False, symlinks=False, upgrade=False, with_pip=False)

  `EnvBuilder`类在实例化时接受以下关键字参数：

  - `system_site_packages` - 一个布尔值，指示系统Python site-packages应该可用于环境（默认为`False`）。
  - `clear` - 一个布尔值，如果为true，将在创建环境之前删除任何现有目标目录的内容。
  - `symlinks` - 一个布尔值，指示是否尝试符号链接Python二进制文件（以及任何必需的DLL或其他二进制文件，例如。`pythonw.exe`），而不是复制。在Linux和Unix系统上默认为`True`，但在Windows上为`False`。
  - `upgrade` - 一个布尔值，如果为true，将使用正在运行的Python升级现有环境，以便在该Python已就地升级时使用（默认为`False`） 。
  - `with_pip` - 一个布尔值，如果为true，则确保pip安装在虚拟环境中。这使用`ensurepip`与`--default-pip`选项。

  在版本3.4中已更改：添加了`with_pip`参数

  第三方虚拟环境工具的创建者可以免费使用提供的`EnvBuilder`类作为基类。

  返回的env-builder是一个对象，它有一个方法`create`：

  `create`(env_dir)

    此方法将包含虚拟环境的目标目录的路径（绝对或相对于当前目录）作为必需参数。`create`方法将在指定的目录中创建环境，或引入适当的异常。

    `EnvBuilder`类的`create`方法说明了可用于子类自定义的钩子：

        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)

    `ensure_directories()`，`create_configuration()`，`setup_python()`，`setup_scripts()`和`post_setup()`可以覆盖。

  `ensure_directories`(env_dir)

    创建环境目录和所有必需的目录，并返回上下文对象。这只是属性（例如路径）的持有者，供其他方法使用。只要指定`clear`或`upgrade`允许在现有环境目录上操作，就允许这些目录存在。

  `create_configuration`(context)

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

  `setup_python`(context)

    在环境中创建Python可执行文件（以及在Windows，DLL下）的副本。在POSIX系统上，如果使用特定的可执行文件`python3.x`，将会创建指向该可执行文件的符号链接`python`和`python3`，除非具有这些名称的文件已存在。

  `setup_scripts`(context)

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

  `post_setup`(context)

    可以在第三方实现中覆盖以在虚拟环境中预安装软件包或执行其他后创建步骤的占位符方法。

  此外，`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目录的名称（`bin`或`Scripts`）。
    - `__VENV_PYTHON__`将替换为环境的可执行文件的绝对路径。

    允许存在目录（用于在升级现有环境时）。

还有一个模块级的方便功能：

`venv.``create`(env_dir, system_site_packages=False, clear=False, symlinks=False, with_pip=False)

  使用给定的关键字参数创建`EnvBuilder`，并使用env_dir参数调用其`create()`方法。

  在版本3.4中已更改：添加了`with_pip`参数

## 28.3.3. An example of extending `EnvBuilder`

以下脚本显示了如何通过实现一个子类来扩展`EnvBuilder`，该子类安装了setuptools和pip到创建的venv中：

    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 environment.

        :param nodist: If True, setuptools and pip are not installed into the
                       created environment.
        :param nopip: If True, pip is not installed into the created
                      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
            environment being created.

            :param context: The information for the 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 env'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 env
            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 environment.

            :param context: The information for the 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 environment.

            :param context: The information for the environment creation request
                            being processed.
            """
            url = 'https://raw.github.com/pypa/pip/master/contrib/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 to create the environment in.')
            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 '
                                                   'environment directory if it '
                                                   'already exists, before '
                                                   'environment creation.')
            parser.add_argument('--upgrade', default=False, action='store_true',
                                dest='upgrade', help='Upgrade the 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)

此脚本也可以在在线下载。