Python3 の venv モジュールはどのように仮想化を実現しているのかを調べてみた

Python3では以下のコマンドで仮想環境の構築とアクティベートを行います。
(参考: https://docs.python.org/ja/3.9/library/venv.html#module-venv)

- 仮想環境の構築
    - `python3 -m venv /path/to/new/virtual/environment`
- アクティベート
    - `$ source <venv>/bin/activate`

Python3.8・mac環境でvenvを実行した際は以下の環境が作られました。


```
$ tree . -L 5
.
└── venv
    ├── bin
    │   ├── Activate.ps1
    │   ├── activate
    │   ├── activate.csh
    │   ├── activate.fish
    │   ├── easy_install
    │   ├── easy_install-3.8
    │   ├── pip
    │   ├── pip3
    │   ├── pip3.8
    │   ├── python -> python3
    │   └── python3 -> /Library/Frameworks/Python.framework/Versions/3.8/bin/python3
    ├── include
    ├── lib
    │   └── python3.8
    │       └── site-packages
    │           ├── __pycache__
    │           ├── easy_install.py
    │           ├── pip
    │           ├── pip-20.2.1.dist-info
    │           ├── pkg_resources
    │           ├── setuptools
    │           └── setuptools-49.2.1.dist-info
    └── pyvenv.cfg
```

# activate の shell を読んでみる

activate の中で仮想環境を実現していると思われたので覗いてみました。
以下が activate の中身です。

![venv_highlight.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110482/ea1ac40d-c770-283b-5c2b-f2bda61c78f8.png)



赤枠部分に以下の特徴が見て取れました。

- 1. VIRTUAL_ENV の環境変数を設定
    - ```VIRTUAL_ENV=<path to .venv>```
    - ※ <path to .venv> には私のvevnを構築した際のフルパスが入っていました。
- 2. コマンドの検索先を`$VIRTUAL_ENV/bin:$PATH`を最優先にする
    - ```PATH="$VIRTUAL_ENV/bin:$PATH"```  
- 3. 定義してあるPYTHONHOMEの無効化

2.によって Pyhon や pip などのコマンドの向き先を仮想環境内のパスに切り替えているのがわかります。
3.によって PYTHONHOMEを無効化することで、設定されていたモジュールの検索先パスを無効化していることが分かります。

ただこれだけでは仮想化の実現に機能が足りません。
モジュールの検索先パスを仮想環境に切り替えるには何をしているのでしょうか。

## モジュールの検索先パスの切り替えの実現方法

Python3はモジュールを検索するパスのリストを持っています。
そのリストが sys.pathです。
(※ 実際は sys.path を参照に到る前にもっと工程があります。 参考:https://docs.python.org/ja/3/reference/import.html#searching)

ところで、sys.path にパスが設定されるのはどこの工程でしょうか。
その疑問を解決してくれている記事を見つけました。
http://hagifoo.hatenablog.com/entry/2013/07/29/132740

しかし、Python2.7の頃の話のようなので、
Python3.8のsite.pyドキュメントを見に行くことにしました。

## site.py のドキュメントを読んでみる

https://docs.python.org/ja/3/library/site.html

以下のような説明があります。

> site.main() 関数の処理は、前部と後部からなる最大で四つまでのディレクトリを構築するところから始まります。 前部では sys.prefix と sys.exec_prefix を使用します; 空の前部は使われません。 後部では、1つ目は空文字列を使い、2つ目は lib/site-packages (Windows) または lib/pythonX.Y/site-packages (Unix と Macintosh) を使います。 前部-後部の異なる組み合わせごとに、それが存在しているディレクトリを参照しているかどうかを調べ、存在している場合は sys.path へ追加します。 そして、新しく追加されたパスからパス設定ファイルを検索します。


よく分からないので、部分的に見てみましょう。

> 前部では sys.prefix と sys.exec_prefix を使用します; 空の前部は使われません。

上記よく分からない....

> 後部では、1つ目は空文字列を使い、2つ目は lib/site-packages (Windows) または lib/pythonX.Y/site-packages (Unix と Macintosh) を使います。

お、 `lib/pythonX.Y/site-packages` が出てきた!

> 前部-後部の異なる組み合わせごとに、それが存在しているディレクトリを参照しているかどうかを調べ、存在している場合は sys.path へ追加します。

`sys.path` へ追加って書いてある!
Python3.8 でも`lib/pythonX.Y/site-packages`を `sys.path` に追加をしてくれているのは、`Lib/site.py` のようですね。


ちなみにこのsite.pyのページに、
venvがsystemのsite-packagesを隠蔽することが書かれていました。

> "pyvenv.cfg" という名前のファイルが上で挙げたディレクトリの 1 つに存在していた場合、 sys.executable, sys.prefix, sys.exec_prefix にはそのディレクトリが設定され、 site-packages もチェックします (sys.base_prefix と sys.base_exec_prefix は常にインストールされているPythonの "実際の" プレフィックスです)。 (ブートストラップの設定ファイルである) "pyvenv.cfg" で、キー "include-system-site-packages" に "true" (大文字小文字は区別しない) 以外が設定されている場合は、 site-packages を探しにシステムレベルのプレフィックスも見に行きません; そうでない場合は見に行きます。


以上が調べてみた結果でございます。

# appendix
- venv 公式ドキュメント
    - https://virtualenv.pypa.io/en/latest/