23
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

PEP 582で仮想環境が不要になる?

Last updated at Posted at 2019-02-16

Goodbye Virtual Environments?という記事に興味を惹かれてPEP 582を試してみました。

従来の仮想環境の課題

学習コスト

さぁ、Pythonをはじめよう...としたときにまずは仮想環境の作り方から入るのはなかなか大変です。
昨今はpipenvPoetryなど高機能なツールがありますが、使い方を覚えるのは容易ではありません。

ターミナルごとの操作

仮想環境を有効にするためにはターミナルごとにアクティベートなどの作業が発生します。

認知的オーバーヘッド

どの場所にどのような環境を構築したのか覚えておく必要があります。

PEP 582 -- Python local packages directory

先のような課題を解決するために、PEP 582が提案されています。PEP 582の概要はつぎのとおりです。

カレントディレクトリ直下に__pypackages__が存在する場合はsys.pathに優先して追加されます。したがって、有効化などの作業が必要ありません。

__pypackages__は次のような構造になっています。

.
└── __pypackages__
    └── 3.8
        └── lib
            ├── package_name

PEP 582を試す

この提案はまだPythonに取り込まれていませんし、その可能性も不明です1
このコンセプトを実装したpythonlocをインストールするとPEP 582で提案された機能を使えます。

pythonlocをインストール

pip install --user pythonloc

pipコマンドは環境に合わせてpip3pythono3 -m pipなどに置き換えてください。

インストールすると次のコマンドが$HOME/.local/binに置かれます。

  • pythonloc: pythonの代替えコマンド
  • piploc: pipの代替えコマンド
  • pipfreezeloc: pip freezeの代替えコマンド

上記コマンドが使えるよう、PATHを通します。

export PATH=$PATH:$HOME/.local/bin

ずっと、これを使いたい場合は.bashrcなどに追加してもよいでしょう。

パッケージをインストール

動作を比較するために2つのディレクトリを作成します。

mkdir project1 project2

pipの代わりにpiplocを使用します。「project1」のディレクトリにdateutilをインストールしてみます。

cd project1
piploc install python-dateutil

pipではインストール先のディレクトリはsite-packagesディレクトリの下に置かれますが、「project1」のディレクトリ階層はつぎのようになります。

~/project1$ tree -L 4
.
└── __pypackages__
    └── 3.7
        └── lib
            ├── __pycache__
            ├── dateutil
            ├── python_dateutil-2.8.0.dist-info
            ├── six-1.12.0.dist-info
            └── six.py

いろいろ試してみる

「project1]のディレクトリからさきほどインストールしたdateutilがインストールされているか確認してみます。pip freezeの代わりにpipfreezelocを使います。

~/project1$ pipfreezeloc 
python-dateutil==2.8.0
six==1.12.0

dateutilをimportしてみます。
pythonコマンドの代わりにpythonlocコマンドを使います。

~/project1$ pythonloc -c "import dateutil;print(dateutil)"
<module 'dateutil' from '/home/driller/project1/__pypackages__/3.7/lib/dateutil/__init__.py'>

問題なくimportできています。
インストールしていない「project2」のディレクトリで同じコマンドを実行してみます。

~/project1$ cd ../project2
~/project2$ pipfreezeloc
~/project2$
~/project2$ pythonloc -c "import dateutil;print(dateutil)"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
ModuleNotFoundError: No module named 'dateutil'

「project2」ではimportできないことから、「project1」にのみインストールされていることがわかりました。

スクリプトを試してみます。import.pyというファイルを「project1」に作成して実行してみます。

import.py
import pprint
import sys

pprint.pprint(sys.path)

import dateutil
print(dateutil)
~/project1$ pythonloc import.py
['/home/driller/project1',
 '/home/driller/project1',
 '/home/driller/project1/__pypackages__/3.7/lib',
 '/usr/local/lib/python37.zip',
 '/usr/local/lib/python3.7',
 '/usr/local/lib/python3.7/lib-dynload',
 '/home/driller/.local/lib/python3.7/site-packages',
 '/usr/local/lib/python3.7/site-packages']
<module 'dateutil' from '/home/driller/project1/__pypackages__/3.7/lib/dateutil/__init__.py'>

問題なく実行できます。ほかのディレクトリではどうでしょうか。

~/project1$ cd ../project2
~/project2$ pythonloc ../project1/import.py 
['/home/driller/project1',
 '/home/driller/project2',
 '/home/driller/project1/__pypackages__/3.7/lib',
 '/usr/local/lib/python37.zip',
 '/usr/local/lib/python3.7',
 '/usr/local/lib/python3.7/lib-dynload',
 '/home/driller/.local/lib/python3.7/site-packages',
 '/usr/local/lib/python3.7/site-packages']
<module 'dateutil' from '/home/driller/project1/__pypackages__/3.7/lib/dateutil/__init__.py'>

ディレクトリを移動しても、スクリプトのディレクトリにsys.pathが登録されているため、importできます。
念の為、ファイルを「project2」にコピーして実行してみます。

~/project2$ cp ../project1/import.py import.py 
~/project2$ pythonloc import.py 
['/home/driller/project2',
 '/home/driller/project2',
 '/home/driller/project2/__pypackages__/3.7/lib',
 '/usr/local/lib/python37.zip',
 '/usr/local/lib/python3.7',
 '/usr/local/lib/python3.7/lib-dynload',
 '/home/driller/.local/lib/python3.7/site-packages',
 '/usr/local/lib/python3.7/site-packages']
Traceback (most recent call last):
  File "import.py", line 6, in <module>
    import dateutil
ModuleNotFoundError: No module named 'dateutil'

「project2」ではdateutilをインストールしていないため、importできません。

以上の結果から次のことがわかりました。

  • 「project1」のディレクトリのみパッケージがインストールされる
  • カレントディレクトリから実行することで、アクティベートの作業をしなくともパッケージがimportできる
  • スクリプトが置かれたディレクトリにsys.pathが登録されるため、そのまま実行できる

sys.pathにはsite-packagesが含まれているため、「project1」は完全に独立した環境ではなさそうです。
site-packages側にパッケージをインストールしてみます。

~/project2$ pip install pytz --user
Collecting pytz
  Using cached https://files.pythonhosted.org/packages/61/28/1d3920e4d1d50b19bc5d24398a7cd85cc7b9a75a490570d5a30c57622d34/pytz-2018.9-py2.py3-none-any.whl
Installing collected packages: pytz
Successfully installed pytz-2018.9
~/project1$ pip freeze
pythonloc==0.1.1.1
pytz==2018.9
~/project1$ pipfreezeloc 
six==1.12.0
python-dateutil==2.8.0

importしてみます。

~/project2$ cd ../project1
~/project1$ pythonloc -c "import pytz;print(pytz)"
<module 'pytz' from '/home/driller/.local/lib/python3.7/site-packages/pytz/__init__.py'>

グローバルにインストールしたパッケージがimportできてしまうので、テストなどの用途には向いてなさそうです。

雑感

  • コンセプトは非常にシンプルで仮想環境の複雑さを解消できそう
  • Python本体のコピーを作成しないため、従来の仮想環境より少しエコ
  • 完全に独立した環境を作るには工夫が必要?(なにか方法が用意されているかも)
  • 複数のPythonバージョンを切り分けたい場合の実装方法が不明

オマケ

CPythonのバイナリがpypackageブランチから試せます。ビルドして実行してみます。

Pythonのバージョはこんな感じです。

~$ python -V
Python 3.8.0a0

sys.pathを確認してみます。

~$ python -c "import sys, pprint;pprint.pprint(sys.path)"
['',
 '/home/driller/__pypackages__/3.8/lib',
 '/usr/local/lib/python38.zip',
 '/usr/local/lib/python3.8',
 '/usr/local/lib/python3.8/lib-dynload',
 '/usr/local/lib/python3.8/site-packages']

__pypackages__sys.pathに優先して追加されていることがわかります。

パッケージをインストールしてみます。piplocに該当するコマンドがないため、--targetオプションを指定します。

~$ mkdir project1 project2
~$ cd project1
~/project1$ pip install pytz --target="__pypackages__/3.8/lib"
Collecting pytz
  Downloading https://files.pythonhosted.org/packages/61/28/1d3920e4d1d50b19bc5d24398a7cd85cc7b9a75a490570d5a30c57622d34/pytz-2018.9-py2.py3-none-any.whl (510kB)
    100% |████████████████████████████████| 512kB 3.9MB/s 
Installing collected packages: pytz
Successfully installed pytz-2018.9
~/project1$ tree -L 4
.
└── __pypackages__
    └── 3.8
        └── lib
            ├── pytz
            └── pytz-2018.9.dist-info

5 directories, 0 files

インストールしたパッケージをimportしてみます。

~/project1$ python -c "import pytz;print(pytz)"
<module 'pytz' from '/home/driller/project1/__pypackages__/3.8/lib/pytz/__init__.py'>

pipコマンドからはまだ認識できていないようです。

~/project1$ pip freeze
~/project1$

pythonコマンドが__pypackages__にインストールしたパッケージを認識しています。
ディレクトリを移動して実行してみます。

~/project1$ cd ../project2
~/project2$ python -c "import pytz;print(pytz)"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
ModuleNotFoundError: No module named 'pytz'

インストールしたパッケージは「project1」にのみインストールされていることがわかりました。

PEP 582が採用された場合、下記の項目はどうなるのか気になるところです。

  • pip installのデフォルトのインストール先がsite-packagesから__pypackages__に代わるのか?
  • デフォルトのインストール先がsite-packagesであるならばpipのオプションが追加されるのか?
  1. 2019年2月時点

23
15
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?