88
85

More than 3 years have passed since last update.

【venv不要】Remote Containersで作る最強のPython開発環境【VSCode/Docker】

Last updated at Posted at 2019-12-22

はじめに

人気言語Top1に輝いたり1、IPA 基本情報技術者試験の試験科目になる2などして注目を集めているPython。

でもちょっと待って!あなたはPythonのエコシステムの恩恵を正しく受けて開発できていますか?

開発環境をうまく作ることで、 入力補間やフォーマッターによって開発が効率化されるだけでなく静的解析によって予期せぬ不具合を未然に防ぐことができ、①簡単 ②爆速 ③安全 にプログラムを書くことができます。

もちろんそのような快適な環境を作るためのHow to記事はたくさんありますが、問題はせっかくオレオレPython開発環境を作れたとしても、それを異なるPC間で共有するのが困難であるということです。

なぜなら、

  • Pythonやpipで入れるモジュールのバージョンを揃えてインストールしないといけない
  • fommter / linter / IDEの拡張機能を揃えてインストール&設定しないといけない

という、開発環境に依存する問題が発生するからです。
個人 / 複数人を問わず、異なるPC間であっても同一の動作をすることが保証されて欲しい
かつ、 テキストエディタでプロジェクトを開くだけで、その開発環境が勝手に構築されて欲しいという要求があります。

ということで、この記事では、VSCodeのRemote Containerを使うことでこの問題を解決します。
本記事で述べる方法を用いると、以下のようなことが実現されます。

  • Win / Mac / Linuxを問わず、全ての環境で同一の動作をすることが保証される3、ポータブルなPython開発環境が作れる
  • 新しい環境でも、プロジェクトを開いただけで静的解析や自動フォーマットなどの全ての設定が完了する

では、やっていきましょう

インストール

本記事では、以下のツールを用います。
他の記事を参考にしながらインストールしてください。

  • VSCode
  • Docker

ターミナルから以下のコマンドが打てれば準備完了です。

$ docker --version
Docker version 19.03.5, build 633a0ea

Remote Containersの設定

まずは、開発用のディレクトリを適当に作りVSCodeで開きましょう。

$ mkdir python-test

スクリーンショット 2019-12-21 16.27.14.png

以下のようなディレクトリ構成を作ります

.
└── python-test
    ├── .devcontainer
    │   ├── Dockerfile
    │   └── devcontainer.json
    ├── .vscode
    │   └── extensions.json
    └── src
        └── main.py

それぞれのファイルには以下の内容をコピペしてください。

.devcontainer/Dockerfile
FROM python:3.7.3-slim

RUN apt-get update \
    && apt-get install -y --no-install-recommends \
    apt-utils \
    gcc \
    build-essential \
    && pip install --no-cache-dir \
    autopep8 \
    flake8 \
    && apt-get autoremove -y \
    && apt-get clean -y \
    && rm -rf /var/lib/apt/lists/*

ここで、 FROM python:3.7.3-slim の部分の数字を変えると、好きなPythonのバージョンを指定することができます。

.devcontainer/devcontainer.json
{
    "name": "Python Project",
    "dockerFile": "Dockerfile",
    "settings": {
        "terminal.integrated.shell.linux": "/bin/bash",
        "python.pythonPath": "/usr/local/bin/python"
    },
    "extensions": [
        "ms-python.python"
    ]
}

ここで、 "name": "Python Project" の部分は好きな文字列にしてしまって構いません。

settings には、 .vscode/settins.json で書くものと同一のものを記述することができます。本来bashのパスやpythonのパスは環境依存であるため個人ごとに設定する必要があるのですが、今回はコンテナ内で実行されるのでパスの情報が既知であり、固定することができるというのがポイントです。

また、 extensions には、VSCodeの拡張機能を加えることができ、ここに記述した拡張機能は自動でインストールされます。VSCodeの拡張機能を強制的にインストールさせることができるのはRemote Containersならではなので、全員にインストールして欲しい拡張機能はここに書きましょう。今回はVSCode用のPython拡張機能を追加しています。

src/main.py
import sys
print(sys.version_info)

Pythonスクリプトはバージョンを出力するものとでもしておきましょう。

.vscode/extensions.json
{
    "recommendations": [
        "ms-vscode-remote.remote-containers"
    ]
}

VSCodeの拡張機能のRecommendationsにRemote Containersを指定します。
注意しなければならないのは devcontainer.json 内のextensionsと違って、記述しても実際にインストールされるわけではありません。ですので、ファイルをコピペし終わったら実際にインストールするためにVSCodeを開き直してください。
すると、右下に以下のようなポップアップが出てくると思います。Install Allを選び、Remote Containerをインストールしましょう。
スクリーンショット 2019-12-21 15.09.13.png

無事、インストールできたらVSCodeの左下に緑色のボタンが表示されます。
スクリーンショット 2019-12-21 14.40.56.png

開くと、このようなメニューがでるので、
スクリーンショット 2019-12-21 14.41.42.png

「Remote-Containers: Open Folder in Container...」を選びます。
フォルダ選択画面がでたら、プロジェクトのディレクトリ(今回の場合はpython-test)を選びましょう。

すると .devcontainer/Dockerfile に記述したコンテナのビルドが自動で走ります。detailsを開くことで 経過を見ることができます。もしbuildにこけてしまった場合もdetailsを見ましょう。
スクリーンショット 2019-12-21 14.45.37.png

ビルドが正常に終わったらそのままコンテナを起動し接続してくれます。コンテナに接続されたらVSCodeの内部ターミナルを開いてください。
スクリーンショット 2019-12-21 14.58.01.png

おや?なにやらシェルのユーザーがrootになっていて、ディレクトリも /workspaces になっていますね。これは、ホストOS(Win / Macとか) のターミナルではなく、ホストOSから独立したコンテナのターミナルに入れていることを示しています。

Docker分かんないよ!という方もいるかもしれませんが、とりあえずPythonを実行してみましょう。

/workspaces/python-test# python src/main.py 
sys.version_info(major=3, minor=7, micro=3, releaselevel='final', serial=0)

FROM python:3.7.3-slim で指定したバージョンのpythonが入っていますね!
これで基本的な設定は完了です。

今後は他のPCで git clone しても、「Remote-Containers: Open Folder in Container...」でコンテナの中に入ることで、同一の環境を再現することができます。

requirements.txtの設定

ここはコンテナの中なので、この中で好きに pip install しても構いません。ここでインストールしたモジュールはホストOSにはまったく影響を与えることがないので、破壊的に開発を進めることができます。

/workspaces/python-test# pip install numpy
/workspaces/python-test# python
>>> import numpy as np
>>> np.__version__
'1.17.4'

また、再度コンテナをビルドして作り直すとインストールしたモジュールは消えます。そこで、開発を進める中でこのpipモジュールはfixしよう!となった場合、Pythonで慣例として使われている requirements.txt を作って、そこに pip install するモジュールを書くこととしましょう。

ディレクトリ構成と requirements.txt の書き方は以下の通りです。

.
└── python-test
    ├── .devcontainer
    │   ├── Dockerfile
    │   └── devcontainer.json
    ├── .vscode
    │   └── extensions.json
    └── requirements.txt
requirements.txt
numpy==1.17.4

requirements.txtには、 pip install する際のバージョンを固定して書くことができます。再現性を担保するために、バージョンは固定することが望ましいです。

そして、 devcontainer.json を以下のようにしましょう。

.devcontainer/devcontainer.json
{
    "name": "Python Project",
    "dockerFile": "Dockerfile",
    "settings": {
        "terminal.integrated.shell.linux": "/bin/bash",
        "python.pythonPath": "/usr/local/bin/python"
    },
    "extensions": [
        "ms-python.python"
    ],
    "postCreateCommand": "pip install -r requirements.txt"
}

postCreateCommand というのが追加されましたね。これは、コンテナが生成された後に実行するコマンドです。今後は requirements.txt に記述したモジュールが必ずインストールされます。

ファイルを更新したら、更新をコンテナに反映させるために、左下の緑色のボタンを押して、「Remote-Containers: Rebuild Container」を選びましょう。

formatter / linterを設定する

続いて、formatter / linterを設定していきます。
pythonのformatter / linterを追加したときの devcontainer.json は以下のとおりです。

.devcontainer/devcontainer.json
{
    "name": "Python Project",
    "dockerFile": "Dockerfile",
    "settings": {
        "terminal.integrated.shell.linux": "/bin/bash",
        "python.pythonPath": "/usr/local/bin/python",
        "python.linting.pylintEnabled": false,
        "python.linting.flake8Enabled": true,
        "python.linting.flake8Args": [
            "--ignore=E402,E501"
        ],
        "python.formatting.provider": "autopep8",
        "python.formatting.autopep8Args": [
            "--ignore",
            "E402,E501",
            "--max-line-length",
            "150"
        ],
        "[python]": {
            "editor.formatOnSave": true
        }
    },
    "extensions": [
        "ms-python.python"
    ],
    "postCreateCommand": "pip install -r requirements.txt",
}

linterとしてflake8、 formatterとしてautopep8を指定し、それぞれE402(Module level import not at top of file)とE501(Line too long)を無視しています。ファイルのセーブ時にformatがかかるようにしています。

これでこんなふうなめちゃくちゃなコードを書いても、

before.py
a    =0
def method1(args)  :
    print(   args)
def method2() :
    if a    ==0:
            method1(   "hoge"   +    "fuga"   )

このように、保存する際に整形してくれます。

after.py
a = 0


def method1(args):
    print(args)


def method2():
    if a == 0:
        method1("hoge" + "fuga")

通常、このようなformatter / linterの設定は .vscode/settings.json に記述します。しかし、 .vscode/settings.json は個人の設定ファイルであるから、 .gitignore に設定しているプロジェクトも多いです。加えて、 pip install flake8 autopep8 を手動で実行する必要があるので、設定ファイルを書いたからといって、formatter / linterが適用されるわけではありません。

一方で、今回はRemote Containersの設定ファイルに書くことができるので、共有もしやすいですし、確実にflake8やautopep8が入っている状態を再現できるので "フォーマットがかかっていないソースコードがコミットされる"ということも防ぐことができます。

ところで、 pip install flake8 autopep8 はどこで実行していたのでしょうか?
答えは .devcontainer/Dockerfile の中にあります。

.devcontainer/Dockerfile
FROM python:3.7.3-slim

RUN apt-get update \
    && apt-get install -y --no-install-recommends \
    apt-utils \
    gcc \
    build-essential \
    && pip install --no-cache-dir \
    autopep8 \
    flake8 \
    && apt-get autoremove -y \
    && apt-get clean -y \
    && rm -rf /var/lib/apt/lists/*

もしあなたが、コンテナ内で pytest を記述したいと思った場合は追加することができます。

.devcontainer/Dockerfile
RUN pip install --no-cache-dir \
    autopep8 \
    flake8 \
    pytest

また、autopep8やflake8は開発時に必要なのであって、実行時には不要です。そうしたモジュールはDockerfileに書いて、実行時に必要なファイルは requirements.txt に書く、という使い分けができます。

静的型解析

最後にPython3.6以降4で使えるType Hintsを導入しましょう。Type Hintsを導入することで、QoLが爆上がりします。

devcontainer.json を以下のようにしましょう
ここでは、Type Hintsの静的解析ツールとして、pyrightを用います。

.devcontainer/devcontainer.json
 "extensions": [
    "ms-python.python",
    "ms-pyright.pyright"
 ],

変更したら、左下の緑色のボタンを押して、「Remote-Containers: Rebuild Container」を選びましょう。

続いて、pyrightの設定ファイルを書きます。
ディレクトリ構成は以下の通りです。

.
└── python-test
    ├── pyrightconfig.json
    └── src
        └── main.py
pyrightconfig.json
{
    "include": [
        "src"
    ],
    "reportTypeshedErrors": false,
    "reportMissingImports": true,
    "reportMissingTypeStubs": false,
    "pythonVersion": "3.7",
    "pythonPlatform": "Linux",
    "executionEnvironments": [
        {
            "root": "src"
        }
    ]
}

pyrightconfig.jsonの詳しい書き方は他の記事を参照してください。(もしかしたら自分が他に書くかも)

ここで、 src/main.py を編集してみましょう。

src/main.py
def hello(name: str, age: int) -> str:
    result1: str = "My name is " + name + ".\n"
    result2: int = "I am " + str(age) + " years old."
    return result1 + result2


result: int = 10
result = hello(name="Otao", age=23)
print(result)

pythonのType Hintsでは、このように関数や変数に型アノテーションを付けることができます。
pyrightを導入した状態で src/main.py を開くと、、、
スクリーンショット 2019-12-21 16.13.41.png
result2はint型なので、str型は代入できないよ!
スクリーンショット 2019-12-21 16.14.01.png
str型とint型の + 演算子は定義されてないよ!!
スクリーンショット 2019-12-21 16.14.10.png
resultはint型で定義されてるのに、そこにstr型を代入しようとしているよ!!!
hello関数の定義も貼っておくね!!!
スクリーンショット 2019-12-21 16.14.22.png
といった風に、型にまつわるエラーを表示してくれます。

Type Hintsを使うことは以下の理由からおすすめです。

  • 型を定義することによって可読性がめちゃくちゃ上がる
  • 予期せぬ型が変数に入ってきて不具合を起こす可能性が下がる
  • VSCode上の入力補完機能が効く

ぜひ導入しましょう。

まぁ不思議なことに(?)このスクリプト、実行はできるんですよねw
(Type Hintsはあくまでアノテーションなので、実行時には無視されます。)

/workspaces/python-test# python3 src/main.py 
My name is Otao.
I am 23 years old.

終わりに

快適なPythonライフを!
(記事は適宜更新します。)

  • 著者情報

  1. 習得したいプログラミング言語、したくない言語, https://tech.nikkeibp.co.jp/atcl/nxt/column/18/00501/111200004/ 

  2. プレス発表 基本情報技術者試験における出題を見直し, https://www.ipa.go.jp/about/press/20190124.html 

  3. 実際にはホストOSによって挙動が違うことがあり、Dockerfileの修正が必要になることもあります。 

  4. 関数へのType Hintsは3.5からあったので正確ではありませんが、変数にも型アノテーションを付けれるようになったのは3.6以降なので、3.6以降を使って欲しいです 

88
85
0

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
88
85