28.3. venv — 仮想環境の作成

バージョン 3.3 で追加.

ソースコード: Lib/venv


venv モジュールは、軽量な “仮想環境” の作成のサポートを提供します。仮想環境には、仮想環境ごとの site ディレクトリがあり、これはシステムの site ディレクトリから分離させることができます。それぞれの仮想環境には、それ自身の Python バイナリ (様々な Python バージョンで環境を作成できます) があり、仮想環境ごとの site ディレクトリに独立した Python パッケージ群をインストールできます。

Python仮想環境に関してより詳しくは PEP 405 を参照してください。

28.3.1. 仮想環境の作成

virtual environments の生成は pyvenv スクリプトを実行することで行えます:

pyvenv /path/to/new/virtual/environment

このコマンドを実行すると、ターゲットディレクトリ (および必要なだけの親ディレクトリ) が作成され、その中に pyvenv.cfg ファイルが置かれます。そのファイルの home キーはこのコマンドを呼び出した Python のインストール場所を指します。このコマンドはまた、python バイナリのコピーを含む bin サブディレクトリを作成します (Windows では、python バイナリを含む Scripts サブディレクトリを作成します)。さらに、lib/pythonX.Y/site-packages (Windows では Lib\site-packages) サブディレクトリも (最初は空の状態で) 作成します。

Windows では、関連する PATH および PATHEXT の設定をしていないなら、以下のように pyvenv スクリプトを呼び出せます:

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

もしくは以下でも同じです:

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

このコマンドを、-h をつけて実行すると、利用できるオプションが表示されます:

usage: venv [-h] [--system-site-packages] [--symlinks] [--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 access to the global site-packages dir to the
                         virtual environment.
  --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 environment directory if it already exists.
                         If not specified and the directory exists, an error is
                         raised.
  --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 機能がどのように起動されたかによって使用法のメッセージが微妙に変わるかもしれません。例えば venv でなく pyvenv について述べるかもしれません。

バージョン 3.4 で変更: デフォルトで pip をインストールします。--without-pip--copies オプションを追加しました。

バージョン 3.4 で変更: 以前のバージョンでは --clear--upgrade オプションが指定されていない限り、対象のディレクトリが既に存在していた場合はエラーを送出していました。 現在は、既存のディレクトリが指定された場合、中身が削除されディレクトリは新規作成されたように扱われます。

作成された pyvenv.cfg ファイルには、include-system-site-packages キーも含まれます。これは venv--system-site-packages オプションをつけて実行されたなら true で、そうでなければ false です。

--without-pip オプションが与えられない限り、pip を仮想環境でブートするために ensurepip が呼ばれます。

pyvenv に複数のパスを与えることもできます。この場合、与えられたそれぞれのパスに、与えられたオプションに従って、同様の仮想環境が作成されます。

一旦 venv が作成されれば、venv のバイナリディレクトリにあるスクリプトで “有効化” できます。スクリプトの呼び出しはプラットフォーム固有です:

プラットフォーム

シェル

仮想環境を有効化するためのコマンド

Posix bash/zsh $ source <venv>/bin/activate
  fish $ . <venv>/bin/activate.fish
  csh/tcsh $ source <venv>/bin/activate.csh
Windows cmd.exe C:> <venv>/Scripts/activate.bat
  PowerShell PS C:> <venv>/Scripts/Activate.ps1

環境の有効化に特別な指定は 必要 ありません。有効化は venv のバイナリディレクトリをパスの最初に加えることで、”python” で venv の Python インタプリタが呼び出されるようにし、インストールされたスクリプトをフルパス無しで実行できるようにするだけです。しかし、インストールされたすべてのスクリプトは有効化しなくても実行可能で、venv の Python で自動的に実行されなければなりません。

シェルで “deactivate” と入力することで、venv を無効化できます。正確な機構はプラットフォーム固有です。例えば、Bash の有効化スクリプトは “deactivate” 関数を定義しています。一方 Windows では、venv が作成されるときに deactivate.batDeactivate.ps1 という別々のスクリプトがインストールされます。

バージョン 3.4 で追加: fish および csh 有効化スクリプト。

注釈

仮想環境 (venv とも呼ばれます) は、その中にインストールされた Python インタープリタ、ライブラリ、そしてスクリプトが、他の仮想環境にインストールされたものから分離されている Python 環境です。そして、デフォルトでは、仮想環境は “システム” 、すなわち OS の一部としてインストールされた Python 内にインストールされた全てのライブラリからも分離されています。

venv は Python 実行ファイルと、それが venv であることを示す幾つかのファイルを含んだディレクトリツリーです。

Setuptoolspip のような一般的なインストールツールは、venv で期待通りに動きます。すなわち、venv が有効な場合は何も指定しなくても自動的にその venv に Python パッケージをインストールします。もちろん、これらのインストールツールを最初に venv にインストールする必要があります。そうするには venv を有効化して ez_setup.py を実行し、続けて easy_install pip を実行します。インストールツールを使う代わりに、ソースパッケージをダウンロードして展開した後、 venv を有効化して python setup.py install を実行しても構いません。

venv が有効な場合 (venv の Python インタープリタを実行している時)、 sys.prefixsys.exec_prefix は venv のベースディレクトリを示します。代わりに sys.base_prefixsys.base_exec_prefix が venv を作った元の Python がインストールされている場所を示します。 venv が無効の時は、 sys.prefixsys.base_prefix と、 sys.exec_prefixsys.base_exec_prefix と同じになり、両方共 venv ではない Python の場所を示します。

When a venv is active, any options that change the installation path will be ignored from all distutils configuration files to prevent projects being inadvertently installed outside of the virtual environment.

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. There should be no need in other circumstances to activate a venv – scripts installed into venvs have a shebang line which points to the venv’s Python interpreter. This means that the script will run with that interpreter regardless of the value of PATH. On Windows, shebang line processing is supported if you have the Python Launcher for Windows installed (this was added to Python in 3.3 - see PEP 397 for more details). Thus, double-clicking an installed script in a Windows Explorer window should run the script with the correct interpreter without there needing to be any reference to its venv in PATH.

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 – 真偽値で、真の場合環境を作成する前に既存の対象ディレクトリの中身を削除します。

  • symlinks – 真偽値で、Python のバイナリ (と必要な DLL や pythonw.exe などのバイナリ) をコピーせずにシンボリックの作成を試みるかどうかを示します。デフォルトは Linux や Unix システムでは True ですが、Windows では False です。

  • upgrade – 真偽値で、真の場合実行中の Python で既存の環境をアップグレードします。その Python がインプレースでアップグレードされたときに用います。デフォルトは False です。

  • with_pip – 真偽値で、真の場合仮想環境に pip がインストールされていることを保証します。--default-pip オプションで ensurepip を使用します。

バージョン 3.4 で変更: with_pip 引数が追加されました。

サードパーティーの仮想環境ツールのクリエータは、EnvBuilder を継承して使うことができます。

返される env-builder オブジェクトには create というメソッドがあります:

create(env_dir)

このメソッドは path (絶対パスあるいは現在のディレクトリからの相対パス) を必要な1つの引数と受け取ります。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)

Each of the methods ensure_directories(), create_configuration(), setup_python(), setup_scripts() and post_setup() can be overridden.

ensure_directories(env_dir)

Creates the environment directory and all necessary directories, and returns a context object. This is just a holder for attributes (such as paths), for use by the other methods. The directories are allowed to exist already, as long as either clear or upgrade were specified to allow operating on an existing environment directory.

create_configuration(context)

仮想環境に pyvenv.cfg 設定ファイルを作成します。

setup_python(context)

Creates a copy of the Python executable (and, under Windows, DLLs) in the environment. On a POSIX system, if a specific executable python3.x was used, symlinks to python and python3 will be created pointing to that executable, unless files with those names already exist.

setup_scripts(context)

プラットフォームに対応した有効化スクリプトを仮想環境にインストールします。

post_setup(context)

サードパーティーライブラリがオーバーライドするための空のメソッドです。このメソッドをオーバーライドして、仮想環境構築後にパッケージのプリインストールなどのステップを実装できます。

これらに加えて、 EnvBuildersetup_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__ は仮想環境の Python 実行ファイルの絶対パスに置換されます。

(既存環境のアップグレード中は) ディレクトリは存在しても構いません。

モジュールレベルの簡易関数もあります:

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

EnvBuilder を指定されたキーワード引数を使って作成し、その create() メソッドに env_dir 引数を渡して実行します。

バージョン 3.4 で変更: with_pip 引数が追加されました。

28.3.3. `EnvBuilder``を拡張する例

次のスクリプトは、セットアップツールと pip を作成した venv にインストールするサブクラスを実装して、EnvBuilder を拡張する方法を示しています。

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)

このスクリプトは オンライン でダウンロードすることも可能です。