はじめに
自分のためのスクリプト言語としてPythonをよく使っている。
スクリプトなので「動けば十分、自分が読めれば十分」でやってきた。
そのため、新しい書き方を知らなかったり、周辺ツールについてもよくわからないまま今に至っている。
要するに、
こんな感じだ。少なくとも開発業においてはPython初心者だといってもいい。
ところで最近チームでPythonを使った開発の機運が出てきた。
不幸なことに、チーム内で一番Pythonに詳しいのは私だ。チームを組むことになる人はPythonを触ったことすらない。
今までの一人環境のままではまずいと思い、Pythonの開発環境の構築について調査を始めた。
というわけで本記事はPythonの開発環境を構築した際の軌跡のメモである。
🔥注意🔥
本記事は構築手順や使い方よりも「どうしてそれを選んだか、あるいは選ばなかったか?」に重きを置いて書いています。
これは、どんな技術を選んだかよりもどうしてその技術を選んだか(理由)の方が重要だと考えているためです。
たぶんこの記事に書いた環境は完璧ではないので後に再構成することになると思いますが、その時に「当時はどう考えてそう構築したか?」を振り返ることになるのでその資料というわけです。
したがいまして、本記事を手順書として使いたい場合はノイズが多いのでそのまま流用することはおすすめしません。また、手順の意味の深いところまでを説明する気はないため、知りたい場合は各自ググってください。
なお、本記事における開発環境とは以下を指す。
- Python環境のインストール(by anyenv & pyenv)
- エディター(Visual Studio Code)のインストールと設定
- パッケージ管理ツール(Poetry)のインストールと設定
- プロジェクトの作成
- 周辺ツールのインストールと設定
方針
環境構築の方法や技術選定には以下のとおり前提と方針を立てて行った。
前提
- OSはMacOS BigSur、シェルはbash
- 要件や用途が明確な"固まった"コードの作成・保守を前提とする(⇔データをこねくり回す試行錯誤性の強いコードは対象としない)
方針
- Python初心者にも楽に構築できること:本質でない工程で工数を取られたくない
- フォーマットとlintを充実させること:均一なコード担保を重視したい
- ローカルでもCIツール上でも動作できること:同上
- Dockerは使わないこと:コンテナー化は次のステップとしたい
Python環境のインストール
anyenvのインストール
Python環境のインストールについてはまだ pyenv がほぼ唯一の現実的な選択肢らしい。
最近は anyenv 経由のインストールが流行っているのと、後述のとおりCI環境ではNode.js環境をインストールすることになるため、はじめにanyenvをインストールすることにする。
-
anyenvをインストールする
brew install anyenv
-
anyenv init
を実行して表示される指示に従う。anyenv 1.1.2
時点でかつシェルにbashを使っている場合は指示内容は以下のコマンドの実行となるecho 'eval "$(anyenv init -)"' >> $HOME/.bash_profile
-
anyenvを初期設定する
anyenv install --init
pyenvのインストール
pyenvをインストールする。
anyenv install pyenv
exec $SHELL -l
Pythonのインストール
今回は使用予定のモジュールの相性の関係から、Python 3.8系列の本記事執筆時点の最新である3.8.6を使う。
本記事執筆時点のpyenvからインストールできる安定版の最新は3.9.0、世間的には3.10.0も見えてきているのでご注意を。
さて、通常は、 pyenv install 3.8.6
でインストールできるはずだがこれでは失敗する。
どうやら、MacOS BigSurにおいてPythonのインストール時ビルドに使っているC++のビルドツールが古いらしい。したがって、Pythonのインストールの前にXCodeとCommand Line Toolsを新しいバージョンにアップデートする手順が必要である。
-
AppleのDeveloperサイト から以下をダウンロードし、インストールする
- Xcode 12.2
- Command Line Tools for Xcode 12
- XCodeを起動し、
XCode
-Preference
ダイアログのLocations
タブを開き、Command Line Tools
がXCode 12.2 (12B45b)
が選択されていることを確認する - python 3.8.6をインストールする
LDFLAGS="-L$(xcrun --show-sdk-path)/usr/lib" pyenv install 3.8.6
エディターのインストールと設定
選定
好きなエディターで書けばいいと思うが、複数人開発では標準エディターを決めておくことは重要だ。問題が起こった時にチーム内でヘルプが受けられる可能性がある。
選定において重視したのは、以下の要素である。
- コードの補完が効く
- コードを書きながらフォーマッターが適用できる(ショートカットキー操作でフォーマットされる)
- 初期設定が面倒くさくない
調査した結果、PyCharm と、拡張機能をインストールした Visual Studio Code(VSCode) が候補に上がった。
能力的にはどちらも同水準だと感じたが、以下の観点でVSCodeを選択した。
- 私はもともとVSCodeを使っていた:なんだかんだいってもこれが一番大きい。ごめん
- PyCharmでは書きながらフォーマッターを適用する機能がない:外部ツール連携を使えばそれっぽい挙動はするが、外部ツール呼び出しによるオーバーヘッドによって動作は遅く、PyCharm上での変更とフォーマッター適用による変更がコンフリクトすることが多々あった
- PyCharmのlint機能をCLIから動かすのが現実的ではない
- ローカル環境:PyCharmはlint機能がPyCharm本体とくっついており、なおかつPyCharmは複数プロセス起動に対応していないため、PyCharmのlintをCLIから実行する場合、一旦PyCharmを終了しないといけない
- CI環境:CI環境上で500MBを超えるPyCharmをインストールするのはちょっとやだ
Visual Studio Codeのインストール
-
VSCodeの公式ページ からバイナリーをダウンロードし、インストールする
-
ターミナルでVSCodeを起動できるようにする
- VSCodeを起動する
-
⌘ + shift + P
を押下する -
シェル ⌘: PATH 内に 'code' コマンドをインストールします/Shell command: Install 'code' command in PATH
を選択する
-
拡張機能をインストールする
# Python Extension Pack code --install-extension donjayamanne.python-extension-pack # Pylance code --install-extension ms-python.vscode-pylance # Code Spell Checker code --install-extension streetsidesoftware.code-spell-checker
Python Extension Pack
はPython系の拡張機能の詰め合わせパックだ。とりあえずこれを入れておけばいい。
Pylance
はPythonのLanguage Serverで、これがコード補完やlintを担ってくれる。
Code Spell Checker
はスペルチェッカーでかっこ悪いスペルミスを防いでくれる。
パッケージ管理ツールのインストールと設定
選定
今までは pip
コマンドだけでやってきたが、やはり requirements.txt
には限界を感じていたのでモダンなパッケージ管理ツールの導入は必須だと考えていた。
調査した結果、以下のツールが候補に上がった。なお、(Ana)condaは不必要なパッケージもインストールされるのが嫌で外した。
- pipenv
- Poetry
- Pyflow
残念ながら私のPython知識/経験では決め手となるほどのパフォーマンスの差を見つけることができなかった。
もう少し正確に書くと、パフォーマンスの差自体はあるのだが、それが私のチームが取り組む現実の問題にどれぐらい影響を与えるかが、未知数だった。
結局Poetryを選んだが、その理由はGoogle検索をするとpipenvとPoetryの比較でPoetry優勢という論調の記事が多かったという消極的なものである。なお、Pyflowはそもそも日本語の情報が極端に少ないという状態だったので採用には至らなかった。
現時点のPoetryを使ってみての感想だが、概ね満足している。強いて言えば poetry run <script>
を実行する時に引数が指定できないのは 難点だ。 難点だが、 Poe the Poet という外部プラグインを使えばこの不満も緩和できそうだ。
(この記事を書いている途中で見つけたのでまだ試せていない。)
Poetryのインストール・設定
公式ページのインストール方法 のとおりインストールする。
一部設定が必要なので 公式ページの設定方法 も参照のこと。
-
Poetryをインストールする
curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -
-
タブ補完を効くように設定する(brewからインストールしたbashの場合。デフォルトのbash、他シェルは上記公式ページ参照のこと)
poetry completions bash > $(brew --prefix)/etc/bash_completion.d/poetry.bash-completion
プロジェクトの作成
いよいよプロジェクトを作成する。今回のプロジェクトの名前は template
とする。
-
プロジェクトを作成する
poetry new --src template
-
template
ディレクトリーに移動するcd <templateディレクトリーのパス>
-
Poetry用のプロジェクト設定ファイル
poetry.toml
を追加するpoetry.toml[virtualenvs] in-project = true create = true
-
Pythonのバージョンを3.8.6で固定化する
pyenv local 3.8.6
-
エディターで
pyproject.toml
を開き、pythonのバージョンが3.8.6(または3.8.z)になっているか確認するtemplate/pyproject.toml... [tool.poetry.dependencies] python = "^3.8" # 3.8.zを使うことを意味する ...
-
ドキュメントはMarkdownを使うため、
README.rst
をREADME.md
にリネームする
ソースコードは template/src/template
フォルダーで管理することになる。
VSCodeでこのフォルダーを認識させるには環境変数PYTHONPATHを適切に設定してやる必要があり、具体的には .vscode/settings.json
と .env
で設定する(後述)。
周辺ツールのインストールと設定
ここからが本題といえるかもしれない。
使用ツールの選定
フォーマッター
調査した結果、以下のフォーマッターが候補に上がった。
この中で一番後発でかつ異色なのはBlackだ。普通フォーマッターというのは多機能であればあるほど設定項目が大量にあるものだがBlackはこれが極端に少ない。
「Pythonは単一の書き方で書けるのが売りなのだからフォーマットだって単一のやり方に収束するはずだ」という理念があるようだ。
フォーマッターの設定はこだわりだすと工数があっという間に溶けることを知っているので、Blackを採用することにした。
問題はimport文である。linterではimport文の順番をチェックするものもあるが、Blackはimport文のソートは行わない。
これについては isort というimportソート専門のフォーマッターツールを使うことで対処することにした。
しかしまだ問題があり、Blackもisortもファイルの中央に書かれたimport文をファイル冒頭に移動することをしない。
この機能を持っているのはautopep8だけのようである。
Pythonでコードを書いているとエディターをスクロールしてファイル冒頭にimport文を書くという操作が煩わしいので、autopep8を頼りに関数やクラスの間にimport文を書くことは多々ある。
この機能は是が非でも欲しかったので、結局、
- VSCode上のフォーマッターはautopep8を使う:VSCode上は単一のフォーマッターしか使用できないためBlackではなくautopep8を優先する
- 最終的なフォーマット処理としてgitへのコミット前にlintと一緒に以下の順番で改めてフォーマッターを適用する
- autopep8:import文のファイル冒頭への移動
- isort:import文のソート
- Black:本文のフォーマット
ということにした。autopep8とBlackで役割が競合しているように見えるが、autopep8はあくまでimport文のファイル冒頭への移動だけが目的である。
linter
調査した結果、以下のlinterが候補に上がった。
- Flake8
- Pylint
- Pyright(Pylance)
- mypy
PyrightはPylanceのコア部分である。VSCodeでPylanceを採用したので必然的に採用となる。
しかしPyrightはFlake8やPylintに比べて現時点でlint機能が貧弱であるのと、Flake8のプラグイン方式による機能拡張は魅力的であるので、Flake8も採用とした。
Flake8のプラグインは大量に存在するが、
awesome-flake8-extensions
で一覧できるのでこのページを見ながらプロジェクトの状況に合わせて追加や削除を続けていく。
直近では、
- hacking
- flake8-annotations
- flake8-classmethod-staticmethod
- flake8-print
- flake8-variables-names
- flake8-simplify
- flake8-SQL
- flake8-use-fstring
を採用し、様子見とする。
mypyはtype hintに特化したlinterだがPyrightがtype hintingのlint機能を持っており競合するため不採用とした。
インストール
Pyright以外
-
template
ディレクトリー内で以下のパッケージをインストール対象として登録するpoetry add -D autopep8 \ black \ isort \ flake8 \ hacking \ flake8-annotations \ flake8-classmethod-staticmethod \ flake8-print \ flake8-variables-names \ flake8-simplify \ flake8-SQL \ flake8-use-fstring \
-
登録したパッケージをインストールする
poetry install
-
VSCodeの設定ファイルにPYTHONPATH、Pythonフォーマッター、lint、スペルチェッカーの設定を追加編集する(ファイルが存在しなければ作成する)
template/.vscode/settings.json{ "[python]": { "editor.codeActionsOnSave": { "source.organizeImports": true }, "editor.insertSpaces": true, "editor.tabSize": 4 }, "cSpell.ignoreRegExpList": [ "[0-9A-Za-zぁ-んァ-ヶ亜-熙纊-黑]+" ], "cSpell.words": [ "noqa", "pyannotate", "pytest" ], "files.eol": "\n", "python.envFile": "${workspaceFolder}/.env", "python.formatting.autopep8Path": "${workspaceFolder}/.venv/bin/autopep8", "python.formatting.provider": "autopep8", "python.linting.enabled": true, "python.linting.flake8Enabled": true, "python.linting.mypyEnabled": false, "python.linting.pylintEnabled": false, "python.pythonPath": "${workspaceFolder}/.venv/bin/python3.8.6", "python.sortImports.path": "${workspaceFolder}/.venv/bin/isort", "terminal.integrated.env.linux": { "PYTHONPATH": "${workspaceFolder}/src" }, "terminal.integrated.env.osx": { "PYTHONPATH": "${workspaceFolder}/src" }, "terminal.integrated.env.windows": { "PYTHONPATH": "${workspaceFolder}/src" } }
-
実行時のPYTHONPATH用の設定ファイルを追加作成する
.envPYTHONPATH = ./src
-
Flake8用の設定ファイルを追加作成する
template/.flake8[flake8] ignore = ANN101,ANN201,ANN202,ANN203,ANN204,ANN205,E501,E722,H101,H201,H301,SIM106,VNE003,W503 max-line-length = 999 exclude = tests/* max-complexity = 10 select_clst1 = CLST101,CLST102,CLST130,CLST131,CLST132
-
isort用の設定とBlack用の設定を追加編集する
template/pyproject.toml... [tool.isort] force_sort_within_sections = true group_by_package = true [tool.black] line-length = 99 ...
補足。
手順の3のVSCode設定では、
- VSCodeで補完を効かせるためにPYTHONPATHにsrcフォルダーを設定しsrcフォルダーを使うこと
- VSCodeからPythonコードを実行するときのPYTHONPATHを.evnから読み取ること
- Python環境はプロジェクト内のvenvを使うこと
- lintを有効化しlinterにautopep8を使うこと
- falke8を使いpylintとmypyを使わないこと
- スペルチェッカーがPythonコード中の日本語コメントに誤爆しないこと
を設定している。
手順の5のFlake8設定では、
- Flake8(拡張プラグインを含む)の指摘のうち、無視してよい指摘のコード番号
- Flake8(mccabe)により計測される循環的複雑度の許容閾値
- 1行あたりの最大文字数チェックの無効化(大きな数字を当てて実質的に無効化)
を設定している。
無視してよい指摘は既存の数ファイルをチェックにかけた結果から設定しているだけなので、今後も調整が必要である。
1行あたりの最大文字数チェックを無効化しているのは、Blackによるフォーマットでコメント行以外は必ず規定文字数内に収まること、コメント行には最大文字数チェックを入れたくないことによる。
2021/07/12追記ここから
Flake8の設定を .flake8
に書いているが、これは pyproject.toml
に統合することができる。
詳細は flake8の設定をpyproject.tomlに統合する を参照のこと。
2021/07/12追記ここまで
手順の6のBlack設定では最大文字数を100文字として設定している(数少ないBlackの設定可能項目である)。
設定ファイルがいろんなファイルに飛び散っていて管理が煩雑だが、 PEP 518 の浸透が進めば最終的には各ツールの設定はpyproject.tomlに集約されることが期待できるらしい。
[CI環境のみ] Pyright
問題はPyrightである。
Pyrightをコアとして持つPylanceがVSCodeにインストールしたため、ローカル環境で開発するだけならインストール不要であるが、今回はCI環境でも動かすことを要件としている。
よって、CIでPyrightを動かすため、別途Pyrightをインストールし動作可能にしておかなければならない。
PyrightはPython製ではなくNode.js製であるため、Node.js環境を構築し、そのうえでPyrightインストールしなければならない。
Node.js環境は nodenv でインストールする。nodeenvは最初にインストールしたanyenvでインストールする。
まとめると、
anyenv
-> nodenv
-> Node.js (with npm)
-> Pyright
という手順である。
既にVSCode上で使えるのに再度インストールしなければならないこと、Python環境なのにNode.jsをインストールすること、インストール手順が煩雑であること、と冗長感が極まっているので、このインストールはCIツール上のみでの手順とし、個人の開発環境では実施しないものとする。
-
nodenvをインストールする
anyenv install nodenv exec $SHELL -l
-
Node.jsをインストールする。なお、バージョンは現時点で最新の
13.10.1
とするnodenv install 13.10.1
-
template
ディレクトリーに移動し、プロジェクト内のNode.js環境を13.0.1で固定化するcd <templateディレクトリーのパス> nodenv local 13.10.1
-
template
ディレクトリー内でPyrightをインストールするnpm install -D pyright
-
pyright
コマンドを定義するため、以下をpackage.json
に追記するpackage.json"scripts": { "pyright": "pyright --version && pyright" },
フォーマット・lintスクリプトの追加
CI環境上で、ソースコードがフォーマット処理が適用されているか、あるいはlintによる指摘がなくなっているかを判断する必要があるため、フォーマット処理の適用とlintはコマンドライン上でも実行できる必要がある。
また、ローカル環境上においても、前述のとおりgitへのコミット前にisortとBlackを適用して最終的なフォーマットを確定する必要がある。この操作もコマンドライン上で行いたい。
上記を実現するスクリプトを template
ディレクトリーに追加する。
#!/bin/bash
# ---------- Set variables
target_file_or_dir='./template'
# ---------- Change directory
script_dir=$(cd "$(dirname "$0")" && pwd -P)
pushd "$script_dir" || exit 1
# ---------- Format python files
# To move import to top.
poetry run find "$target_file_or_dir" -name '*.py' -exec autopep8 --in-place '{}' \;
# To sort import.
poetry run isort "$target_file_or_dir" && \
# To format python code.
poetry run black "$target_file_or_dir" && \
# ---------- Lint python files
if type "node_modules/.bin/pyright" >/dev/null 2>&1; then
npm run pyright "$target_file_or_dir"
fi
poetry run flake8 --config .flake8 "$target_file_or_dir"
# ---------- Change directory
popd || exit 1
各コマンドの冒頭には poetry run
を付与し、この template
プロジェクト環境内のツールを使うことを強制している(参考)。
本当はPythonのスクリプトとして書きたかったが、各ツールのPythonコードを読み解くよりCLIの方が情報が豊富で簡単だったためシェルスクリプトとして実現している。
おわりに
一応体裁は整えたが、まだ道半ばと感じている。ちょくちょく更新するかもしれない。
「もっと良いやりかたがあるよ」 というコメントをお待ちしております❗