Python開発環境メモ(2019)
これは何
2019/3現在における,Pythonとそれを取り巻くツールを活用した開発における,個人的プラクティスをまとめたものです.主にpuddingが人に教える時に自分で参考にするためのものです.
正確性にはできるだけ万全を期していますが,間違っていたらコメントして欲しいです.
言いたいこと
無駄に長い記事を読みたくない人向けに簡潔にまとめておきます
- 自分の環境をできるだけ把握しておく
- Python 2.xを使うな.Python 3.5未満を使うな.
- Pyenvで複数バージョンを導入
- Pipenvで仮想環境とパッケージを管理
- エディタはPyCharmかVSCodeがおすすめ
Pythonとは
スクリプト言語であり,コンパイルによって実行ファイルを作成するのではなく,インタプリタと呼ばれるプログラムによってソースコードを読み取り,順次実行する形式を取ります.
大きく分けて以下の2つのバージョンが現在でも使われており,環境によって色々かわって来ます.
- 2.7.x:早く滅びろ
- 3.x:3.5以下は早く滅びろ
これらにはある程度の文法の互換性が存在しますが,だいたいSyntax Errorで落ちるので,注意してください.今から使うならPython 3.6以降以外の選択肢はありません.もし職場等でPython 2.xを使うことを強制されたなら,落ち着いてハラスメント窓口に駆け込むか,退職願いを書いて転職した方が良いでしょう.
python というコマンド
まず,Python初学者(というかプログラム初学者)に知っていて欲しい事があります.それは
python
や python3
というコマンドは魔法ではなく,
単にそのシステムのどこかにある実行可能なバイナリファイルを指しているだけ
ということです.つまり,python
という実行ファイルが存在していなければ当然Not found
みたいなことを言われてしまうし,python
コマンドの指す実体がPython 2.xかもしれないしPython 3.xかもしれません.当たり前のことですが,Python 2.xに対してPython 3.xの文法で書いたPythonスクリプトを渡しても,Syntax Errorにより実行できない場合がほとんどでなので,自分の今の環境における python
コマンドとは一体どこを指しているものなのか常に意識した方が良いでしょう.
複数バージョンの共存
さて,前節においてpython
というコマンドは,どこかにある実行ファイルを指していると言いましたが,これはつまり,python
というコマンドのさす場所を変えれば,同じpython
というコマンドで全く別のバージョンのPythonを実行できるということを意味しています.これにより自分の環境で任意のPythonバージョンを共存させることを目指したのがPyenvです.
残念ながらWindowsでは利用できませんが,WSLやMac,Linuxなどでは簡単に利用できるため,多少ちゃんと開発をするならばぜひ導入してほしいツールです. Pythonしか開発しない!なんて人はたぶんいないので,このpyenvのような~~env系を簡単に扱えるラッパーであるanyenvをインストールするのがおすすめです.以下のURLを参考にインストールしてください(まだ古いリポジトリの話がネット上には散見されるので注意してください).
# pyenvをインストール $ anyenv install pyenv # シェルを再起動 $ exec $SHELL -l # Pyenvコマンドが使えるようになっていれば成功 $ pyenv versions * system
このsystemというのは,そのOSにインストールされている(macならデフォルトで入っているものや,brewでインストールした)Pythonを指しています.
バージョンを指定してインストール
Pyenvは任意のバージョンのPythonのインストールと,python
(またはpython3
)コマンドの指す実体を変えるだけのものであるので,まずはPythonをインストールする必要があります.インストール時にビルドするため,依存ライブラリが必要となることに注意が必要です.macOSでは少し厄介になることが多く,特に最新のバージョンのmacOSにアップグレードすると大抵死ぬので,そういうときはpyenvのリポジトリのIssueやWik*1を読むと良いでしょう.
# インストールできるバージョンの一覧 $ pyenv install --list # とりあえずPython 3.7.2をインストールする # 依存関係をインストール $ brew install openssl readline sqlite3 xz zlib # Macでは以下の様にする $ CONFIGURE_OPTS="--with-openssl=$(brew --prefix openssl)" pyenv install 3.7.2
pyenvには「global」と「local」という考え方があります.globalは,そのシステム内でpython
コマンドを使ったときに基本的に呼ばれるPythonのバージョンを指定し,localは特定のディレクトリ以下でpython
コマンドのバージョンを上書きすることができます.
# python -> システムにインストールされているPython 2.7.x # python3 -> Python 3.7.2 # になるようにグローバルを設定する $ pyenv global system 3.7.2 $ python -V Python 2.7.14 $ python3 -V Python 3.7.2 # local-testディレクトリ以下で使うバージョンを変える $ mkdir local-test $ cd local-test # .python-versionというファイルが作られる $ pyenv local 3.7.2 $ ls -a | grep python-version .python-version $ python -V Python 3.7.2
これは個人的な考えですが,基本的にglobalはsystemのPythonを参照するようにし,pyenvはあくまで任意のバージョンのPythonを簡単に導入するためのもの,と割り切った方が何かとトラブルが少なく済むと思います.
$ pyenv global system
実際に呼ばれるPythonのパス
pyenvはPyenvのインストールされたディレクトリ以下にあるshims
ディレクトリ以下に,インストールされた各バージョンのPythonインタプリタに対してシンボリックリンクを張ります.そのため,pythonコマンドで呼ばれるPythonがどこにあるかを調べようとしても,すべてそのシンボリックリンクを指してしまいます.本当のパスを知りたいときはpyenv which
を使います.
$ which python3 {{ pyenvのルートディレクトリ }}/shims/python3 # 本当のパスを調べたいとき $ pyenv which python3 {{ pyenvのルートディレクトリ }}/versions/3.7.2/bin/python
仮想環境
さて,複数バージョンの共存は出来るようになりました.次は,各プロジェクトごとにPython環境を分けましょう.プロジェクトとは,例えばあるWebアプリケーションなど一つの機能のまとまりを指します.GitHubのリポジトリ==プロジェクトと考えて良いです.そして仮想環境とは,まぁ要するに各プロジェクトごとにそれぞれPythonインタプリタを用意する,ということを指します.プロジェクトごとに新しくPythonをインストールするというイメージです.これには以下の様なメリットが上げられます.
- 問題の切り分けを簡単にできる
- モジュールの依存関係,バージョン違いによる挙動の変更など
- 必要なモジュールのみから構成されるため,IDE等への負荷軽減になる
- 自分以外のメンバーも同じ環境を再現しやすい
そのため,本当に簡単なスニペットレベルのコードを除いてほぼ必須だと思っています.僕個人は使ったことのないモジュールを試用するとき,標準ライブラリだけでは足りないレベルの使い捨てスニペットを作るときなど比較的軽い用途でもどんどん環境を切り分けています.
Pythonでは非常にややこしいことにこれを実現する方法が複数存在し,どれがベストかは人によると思いますが,個人的な見解を以下に書いておきます.
Pyenv + Pyenv-virtualenv
pyenv-virtualenvというプラグインを導入しPyenvで仮想環境を作る.
- 良いところ
- 操作をPyenv系のコマンドに集約できる
- Pythonのバージョンを変更できる
- 微妙なところ
pyenv versions
コマンドの一覧がどんどん圧迫される- 結局パッケージの管理はpip
- Windowsでは使えない
# pyenv-virtualenvをインストールする $ git clone https://github.com/pyenv/pyenv-virtualenv.git $(pyenv root)/plugins/pyenv-virtualenv # `pyenv virtualenv {{ バージョン }} {{ 環境名 }}`で作成できる $ pyenv virtualenv 3.7.2 test $ mkdir test-pyenv && cd test-pyenv # test環境のpythonが呼ばれるようになる. $ pyenv local test (test) $ pyenv which python {{ pyenvのルートディレクトリ }}/versions/test/bin/python # パッケージのインストール (test) $ pip install {{ パッケージ名 }}
Venv(or virtualenv)
Python3.3から標準化された(virtualenvはそれまでpipで導入できた)仮想環境作成モジュール
- 良いところ
- Python公式であり,(venvなら)大抵最初から使えてお手軽
- 微妙なところ
- 結局パッケージの管理はpip
- たまにVenvが有効化されていない場合がある
- Python2と3で使い勝手が変わるのがつらい(とはいえもう2.xは死んだ)
- VenvはあるバージョンのPythonに付属するモジュールであり,そのバージョン以外の仮想環境を構築出来ない.
$ python3 -V Python 3.7.2 $ mkdir test-venv && cd test-venv # `python3 -m venv {{ 環境名 }}`でカレントディレクトリ以下に # その環境名で仮想環境のディレクトリを作成する. $ python3 -m venv .venv $ ls -a | grep venv .venv # 仮想環境をアクティベート $ source .venv/bin/activate (.venv) $ which python {{ カレントディレクトリ }}/.venv/bin/python # パッケージのインストール (.venv) $ pip install {{ パッケージ名 }} # 仮想環境から抜ける (.venv) $ deactivate
Conda
Anaconda/Minicondaのパッケージマネージャ兼環境管理ツール.ビルド済みの様々なモジュール・ライブラリを内包する一つのPythonディストリビューション.科学計算ライブラリ系にかなり特化しており,依存ライブラリの用意が難しいWindowsでは非常に便利.
- 良いところ
- パッケージ管理と環境構築が同じコマンド体系
- Pythonのバージョンも一つのモジュールのバージョンのように変更できる
- 微妙なところ
- Anacondaの場合容量がびっくりするほどデカい
- condaでインストールできないパッケージはpipでインストールすることになり,パッケージの管理が分散してしまう
- 仮想環境のアクティベーションのためにトリッキーな小技が必要になることが多い
- データ分析界隈で使われることが多いが,初心者のやってみた記事が多すぎて検索結果が大抵地獄
# pyenvでMinicondaをインストールする. $ pyenv install miniconda3-4.3.30 # Minicondaが仮想環境を作成するディレクトリを`/Users/{{ ユーザ名 }}/.conda/envs`にする $ echo -e "envs_dirs:\n - $HOME/.conda/envs" >> $HOME/.condarc # 検証用のディレクトリを作る $ mkdir test-conda && cd test-conda $ pyenv local miniconda3-4.3.30 # `conda env create -n {{ 環境名 }}`で作成する $ conda create -n test # 作成した環境には何もないのでPythonからインストールする $ conda install -n test "python==3.7" # 仮想環境をアクティベートするconda activateコマンドを有効にする $ source $(conda info --root)/etc/peofile.d/conda.sh $ conda activate test (test) $ which python /Users/{{ ユーザ名 }}/.conda/envs/test-env/bin/python # パッケージのインストール (test) $ conda install {{ パッケージ名 }} # condaで見つからない場合は (test) $ pip install {{ パッケージ名 }} # 仮想環境から抜ける (test) $ conda deactivate
Pipenv
requestsモジュールを作ったKenneth Reitz氏が開発されたpipとvirtualenv,そしてpyenvのラッパー.Rubyを書いている人にはPython版のbundlerだと思って貰えるとよい.
- 良いところ
- パッケージの管理と仮想環境の作成が同一のコマンド体系で扱える
pipenv run
を使うことで,いちいち仮想環境をアクティベートする必要がない- pythonへのパスを指定するとそのバージョンをベースにした環境を作れる
- 微妙なところ
- これ一つでだいたい片が付くが,pipでインストールするライブラリを書く時はpipがPipfileを認識できないので,setup.pyを書いて自分で依存関係を記述しなければならない.
# pipを使ってpipenvをインストール $ pip install pipenv $ mkdir test-pipenv && cd test-pipenv # 仮想環境を`~/.virtualenvs`ではなく,`pipenv install`したディレクトリの直下に作る場合,以下を指定 # $ export PIPENV_VENV_IN_PROJECT=true # Python 3.7の仮想環境を作る $ pipenv install --python 3.7 # または直接Pythonへのパスを参照しても良い # $ pipenv install --python $(pyenv which python3) # カレントディレクトリに`.venv`ディレクトリができる $ ls -a | grep .venv .venv # PipfileとPipfile.lockによって管理されている $ cat Pipfile [[source]] name ="pypi" url = "https://pypi.org/simple" verify_ssl = true [dev-packages] [packages] [requires] python_version = "3.7" # `pipenv run`で仮想環境下のコマンドを呼べる $ pipenv run python -V Python 3.7.2 # パッケージのインストール $ pipenv install {{ パッケージ名 }} # 従来通り仮想環境をアクティベートすることもできる $ pipenv shell (.venv) $ python -V Python 3.7.2 # 仮想環境から抜ける (.venv) $ exit
Poetry
最近標準化されたpyproject.toml
を使って管理します.Pipenvと違い,パッケージングまで面倒を見てくれるため,setup.pyを別で記述したり,細かいパッケージング処理を自分で書く必要が無いというのが最も大きな違いです.他にも色々と利点が挙げられているようです.
- 良いところ
- pipでインストールするようなモジュールを書きたい場合にパッケージングの面倒を見てくれる
- どうも依存関係の解消がPipenvより上手く出来ているらしい
- 微妙なところ
$ curl -sSL https://raw.githubusercontent.com/sdispater/poetry/master/get-poetry.py | python # Poetryのコマンドにパスを通す $ echo "export PATH=$PATH:$HOME/.poetry/bin" >> ~/.bashrc $ source ~/.bashrc # 仮想環境をプロジェクト直下に作る $ poetry config settings.virtualenvs.in-project true # 新しいプロジェクトを作る $ poetry new test-poetry $ cd test-poetry # 以下の様なファイル群が自動生成される $ tree -L 2 . ├── README.rst ├── poetry.lock ├── pyproject.toml ├── test_poetry │ └── __init__.py ├── test_poetry.egg-info │ ├── PKG-INFO │ ├── SOURCES.txt │ ├── dependency_links.txt │ └── top_level.txt └── tests ├── __init__.py └── test_test_poetry.py # このプロジェクトで使うPythonのバージョンを指定する $ pyenv local 3.7.2 # このプロジェクトで使うPythonのバージョンをpyproject.tomlに設定する.今回は3.7. $ sed -i '' 's/^python = "\^[0-9]\.[0-9]"/python = "^3.7"/g' pyproject.toml # 仮想環境を構築する $ poetry install # pipenv runと同等のコマンド $ poetry run python -V Python 3.7.2 # 仮想環境をアクティベート # 僕の環境では上手く仮想環境のpythonへパスが通らず,正常に動作しませんでした…… $ poetry shell
参考
まとめ
Pyenv+pyenv-virtualenvで作った環境が結局今どのバージョンを使っているのか分からなくなったり,venvを素のまま使っていてこの環境はバージョン何だったっけとなったりしていい加減嫌になったので,
- Pyenvで任意のバージョンをインストール(インストールするだけ)
- Pipenvで仮想環境作成
が一番しっくりきました.今のところ,これで困るようなフェーズはほとんど遭遇していません.pipenv run
で大抵うまくいくという安心感が良いです.が,Poetryの網羅する範囲が非常に広いため,今後Poetryに移行していく可能性はあります.
パッケージの管理
ここでパッケージとは,pip
コマンド等でインストールできる,何らかの機能が実装されたPythonモジュール群を指しています.かつてはeasy_installなどもありましたが,現状ではpip一択です.Pipは,PyPI(Python Package Index)というサードパーティー製ライブラリを管理するサーバから,指定したパッケージ名を検索,取得してインストールしてくれます.
前述のPipenv,Poetryはパッケージの管理まで面倒を見てくれるため,これらのどれかを使うことをオススメします.これらは裏側でpipを使用してパッケージをインストールしてくれます.PipenvとPoetryはそれぞれ,インストールするパッケージやそのバージョンについての情報が含まれたPipfile(Poetryならばpyproject.toml)が自動的に作られるため,ユーザ自身がそれらの管理について気にする必要はほとんど有りません.
# Pipのみの場合 $ pip install requests # Pipenvの場合 # パッケージをインストールすると勝手にPipfileとPipfile.lockが更新される $ pipenv install requests # 開発用パッケージの追加 $ pipenv install --dev pytest # Poetryの場合 # パッケージをインストールすると勝手にpyproject.tomlとpoetry.lockが更新される $ poetry add requests # 開発用パッケージのインストール $ poetry add --dev pytest
開発環境を再構築するために
再構築という作業を可能にしておくことは色々な意味で有用です.
- 自分のため
- 急にマシンがぶっ壊れる,環境が壊れたのでやりなおすなど,再構築は大抵ハプニングのときに生じる.すぐに開発を再開できるようにしておく
- 環境を整理し,常にクリーンに保てるように注意しておくことができる
- 一緒に開発してくれるメンバーのため
- 全員が同じPCで編集していては仕事が進まない.メンバーが同等の環境を再現できる様にしておく.
- バグや実験の再現性
- 研究でも開発でも再現性は重要です.
READMEにちゃんと構築の仕方を簡単で良いので書いておきましょう.
pip freeze > requirements.txt
pipしか使えない場合です.これまでのPython開発(少なくとも3年くらい前まで)では,requirements.txtというファイルが多く使われていました.以下の様な非常に単純な形式を取っています.
# パッケージ名とバージョン制約から成る requests==2.21.0 pipenv==2018.11.26
これはpip freeze
という,pipでインストールしたモジュールの一覧を吐き出すコマンドの出力とフォーマットが一致しており,以下の様に保存・再構築ができます.
# 今インストールされているパッケージを出力して保存 $ pip freeze > requirements.txt # 一覧からインストール $ pip install -r requirements.txt
しかし,この方法にはいくつかの欠点があり,現状あまりオススメはしません(pipenv等を使えない場合は仕方有りません…)
pip freeze
はインストールしたものを全て出力するため,インストールしたライブラリの依存先まで全て表示されてしまい,どれが本当に必要なものか分かりづらいpip install -U {{ パッケージ名 }}
でバージョンをアップグレードした際にrequirements.txtを更新するのを忘れがちになる- 開発時のみに必要なパッケージ(テスト用のライブラリなど)を別で列挙したrequirements-dev.txtのようなファイルを作ることになる
- そもそも指定したバージョンをインストールするだけなので,本当に依存関係の解消をしているわけではない
より詳細なバージョンコントロールについては参考のリンク*3を見てみてください.
conda env export
ドキュメントにそのまま載っている方法*4です.
# 環境の情報を出力 $ conda env export -n {{ 環境名 }} > environment.yml # 出力された情報から環境を作る $ conda env create -f environment.yml
environment.ymlは以下の様になります.
# 環境名 name: test-env channels: - defaults # 必要な依存関係 dependencies: # "パッケージ名=バージョン=ビルド"の形式 - numpy=1.16.2=py37hacdab7b_0 - python=3.7.0=hc167b69_0 # 仮想環境へのパス prefix: /Users/ユーザ名/.conda/envs/test-env
この方法は,同じOS,同じプラットフォーム,同じアーキテクチャのマシン間で環境を共有するには良い方法です.ビルド番号まで含めて再現されるためです.しかし,環境特有のビルドを含むもの[^mkl]をインストールしていると他の環境ではエラーになる場合があります.これを解決する手段は公式からは提供されていないので,もし必要な場合は主要なモジュールのみを書いて,バージョン番号のみ指定する事が出来ます.
name: test-env-generic channels: - defaults dependencies: # "パッケージ名=バージョン"の形式で書く - numpy=1.16.2 - python=3.7.0 # 省略
pipenv install or poetry install
Pipenvで管理している場合,PipfileとPipfile.lockをリポジトリに含めておけば大抵上手くいきます.Poetryの場合もpyproject.ymlとpoetry.lockを含めておけば良いです.
# Pipfileのあるディレクトリで実行する $ pipenv sync # 開発用パッケージを含める場合 $ pipenv sync --dev
エディタ
何でコードを書くかはしばしば宗教戦争となりがちですが,僕個人は
- 簡単な使い捨てスニペット → Neovim
- それ以外 → PyCharm
という棲み分け(?)をしています.この記事を参考にする人の多くはVimは慣れていないと思いますので,代わりにVSCodeを推していきます(実際たまに使っています)
PyCharm
JetBrains製のPython用IDEです.Windows,Mac,Linuxをサポートしており,3つのエディションがあります.
バージョン | 価格 | 特徴 |
---|---|---|
Professional | 有料・サブスクリプション制 | リモートインタプリタを使用可能,各種フレームワークのサポートなど |
Community | 無料 | - |
Education | 無料 | 機能はCommunityと同等.教育用で学習コースが付属. |
ややこしいのですが,学生はアカデミックアカウントの登録を行うことで,Professionalを無料で使えます*5(Educationは元から無料です).
良いところは
- ほとんど設定無しにデフォルトのLinter,Formatterでそれなりに綺麗にコードを書ける
- 補完候補表示の精度が良い
- デバッグがめちゃくちゃやりやすい
- 設定無しに型ヒントによる文法チェックと補完が有効
- IDEらしくこれ一つで完結するため,ターミナルの出番が少ない
微妙なところ
- やはり単純なエディタよりメモリの消費が激しい
- 動作中は軽いが,仮想環境のモジュールに対するインデクシング処理が激しく重く,また,これが動いている間は補完やシンタックスハイライトが死ぬ
- 慣れると他のものを使えなくなるので学生の間に慣れると将来の出費が確約されてしまう
VSCode
Microsoft製のOSS.Windows, Mac, Linuxで動く超人気テキストエディタ.実際とてもよく出来ているし,軽い.
良いところ
- 豊富なツールサポート(Git,Linter,Formatter,仮想環境…etc)
- InteliSenseがよく出来ている
- 比較的軽い
- 豊富な拡張機能
微妙なところ
- 設定が見づらい上に量が多すぎて検索しないとやってられない
- Formatterなどを毎回そのプロジェクトの仮想環境にインストールしないといけない.
正直そこまで弱点がない良いエディタだと思います.
コーディングの補助
Pythonにはコーディング規約としてPEP8というものがあります*6.コーディング規約というのは,複数人で開発する際に個人間で書き方に細かい違いが出てしまわないように,あらかじめ制定されたコーディングの際のルールです.これに従わなくてもPythonとしての文法が保たれていれば実行できますが,コードの保守性,クオリティを担保するため従っておくべきでしょう. 書いているといつの間にかある程度覚えてきますが,そのチェックを毎回人が行うことは容易ではありません.そういう退屈なことはPythonがやってくれます.
Linter
Lintとは文法チェックのことを指します.コードを読み取り.コーディング規約にちゃんと従っているかを検証してくれます. Pythonでは様々なLinterが実装されていますが,代表的なもののみ示します.
- pycodestyle
- 元々はpep8というモジュール名だったが,ややこしいため最近名前が変更された
- blog.amedama.jp
- flake8
- pycodestyleとpyflakes,mccabeのラッパー.
- pyflakesはエラー解析を行い,未使用の変数や使われていないimport文を検知する
- mccabeはifやforなどの複雑さを評価する循環的複雑度を算出する
- minus9d.hatenablog.com
- pylint
- VSCodeではデフォルトで設定されているLinter.
- 上記2つより少し厳しい
- mypy
個人的にはいいかんじに文法チェックとコーディング規約のチェックをやってくれてうるさすぎないflake8 + 型チェックのためのmypyの組み合わせが,バランスも良いと思います.
VSCodeでの設定
PyCharmでは面倒臭がってデフォルトの設定から動かしていない*7ので,VSCodeの設定例だけ示しておきます.なお,前提としてVSCodeにはPythonの拡張機能がインストールされているものとします.
Cmd
+ ,
で設定を開き,以下の項目を検索して値を変更します.
python.linting.pylintEnabled
:チェックを外すpython.linting.flake8Enabled
:チェックを入れるpython.linting.mypyEnabled
:チェックを入れるpython.linting.lintOnSave
:チェックを入れる
~~ is not installed
みたいなエラーが出ると思うので,それぞれインストールします.
$ pipenv install --dev flake8 mypy
単純なFizzBuzzのコードですが,わざと汚く書いてみました.
問題のある箇所に赤の波線が引かれています.カーソルを合わせると内容が出ます.例えばimportしているけど使っていないosモジュールについての警告が以下です.Quick Fixを押すと修正……され…ません…*8
また,i % 5
をi % "hoge"
にわざと間違えてみると型チェックにより警告が出ます.
なお,Quick Fixを押しても解決はされません…
Formatter
Linterがコードには手を加えない文法チェッカであったのに対して,Formatterは実際にコードを自動で書き換え,適合するスタイルに書き換えてくれます.これもまた複数の実装が存在します.
- autopep8
- PEP8に準拠するようにフォーマットしてくれる
- おそらく最も有名
- yapf
- Yet Another Python Formatterの略.Google謹製のFormatter.
- 設定が豊富.
- フォーマットの例:https://yapf.now.sh
- black
- つい最近出てきた,比較的新しいFormatter.
- 大変ストイックで,設定できるのが1行当たりの文字数しかない.
- フォーマットの例:https://black.now.sh
ドキュメントの豊富さと,導入の簡単さからautopep8を推します.pycodestyleのチェックに通るようにしてくれる程度で過度なスタイルを強要してこないのもカジュアルに書けて嬉しいところです.
VSCodeでの設定
Cmd
+ ,
で設定を開き,以下の項目を検索して値を変更します.
python.formatting.provider
:autopep8
を選択editor.formatOnType
:チェックを入れるeditor.formatOnSave
:チェックを入れる
~~ is not installed
みたいなエラーが出ると思うので,それぞれインストールします.
$ pipenv install --dev autopep8
Cmd
+ Shift
+ P
でコマンドパレットを開き,formatと検索して実行するとフォーマットされます.
formatOnType
を有効にしているので,打ち込みながら勝手に修正されていきます.
GitとPython
近年,コードはGitで管理というのが当たり前です*9.Pythonで書かれるプロジェクトもよくGitで管理されますが,ときどき悲しくなるリポジトリを見かけます.それは
- 仮想環境が丸々入っている
*.pyc
や__pycache__
が含まれている- 環境を再構築するための情報が無い
などです.これらは簡単に避けることが出来ます.
Git監視下に置かない方が良いファイル
.gitignoreというファイルをプロジェクト直下に記述します.gitignoreすべきファイルの参考となるものが,GitHub公式に上がっています.基本はこれをコピペで良いでしょう.
面倒臭いときは以下のものを足しておけば十分かと思います
# 仮想環境の排除 .venv venv/ # pyenvのバージョンファイル .python-version # キャッシュなど __pycache__/ *.py[cod] *$py.class # mypyのキャッシュ .mypy_cache/
追加しておくべきファイル
requirements.txt,Pipfile,Pipfile.lockなどパッケージにまつわるファイルは追加しておくべきです. また,
などもあると良いでしょう.他に,.pylintrc
などのlintの設定なども共有のために含めておくことをオススメします.
開発開始までの流れ
- プロジェクトのディレクトリを作成し,
pipenv install --python 3.7
pipenv install --dev flake8 autopep8 mypy
- VSCodeかPyCharmで開く
- コードを書く
まとめ
長々と書いてしまって本当にすいませんという気持ちで一杯です. これはこうした方がいい,setup.pyについて書かなさすぎだろ等,様々な意見があると思いますのでコメント等頂ければ追記していきたいです.
*2:Support Poetry depenedency manager · Issue #1871 · Microsoft/vscode-python · GitHub
*7:PyCharmではデフォルトでPEP8準拠の警告,型チェック等がFormatterと共に装備されています.
*8:なんで?
某botのこれまでとこれから
はじめに
これはあくあたん工房Advent Calendar 2018 の18日目の記事です. 僕はこれまでこのAdvent Calendarではずっとあくあたん工房の紹介をしてきました.少しでも興味があれば,他の記事も見て頂ければと思います.
さて,この記事は,
- 某botの開発の歴史: どういう経緯で作って,どんな技術を使ってきたのか
- 何がつらいのか: 開発面・運用面のつらい話
- これからどうしていくのか: 卒業までにやっておきたいと思っていること
の3本立てでお送りします.この記事を読んでいる弊学の人は一発でわかるであろう,某便利bot*1の作者は僕なのですが,色々悲惨でつらい感じなのでここで精算してスッキリしようという感じです.
某botの開発の歴史
そもそもbotを作ったきっかけ
- 大学の公式サービスが使いづらくて腹が立った
よくある話ですが,大抵大学のサービスっていうのはレガシーで,見づらくて,把握しづらくて,カオスです.
とにかくこういうのは待ってたって良くならないので,自分でなんとかするしか無いのです.というわけで作りました.
第一世代
Pythonを使用して作りました.当初の構成は簡単で,
- cronを利用して定期的にWebページをスクレイピング
- データベースで情報を管理
- 更新や新規情報の追加があればツイート
たったこれだけでした.この頃は確かまだPython 2.7を使ってRaspberry Piの上で動かしていて,ちょっと尖ったことと言えばSQLAlchemy*2を使ってSQLを書かずにやっていたことくらいでした.
やりたかったこととしては,
- 頻繁に追加される情報の取得
- 既存の情報の更新があった場合その差分の取得
- それらをツイートする
の3つなのですが,結局差分をとるところを実装しないまま終わってしまっています.
第二世代
まぁ人間しばらくすると満足いかなくなるもので,第一世代を運用していたときの僕は
など,たくさん不満を抱えていました.なのでしばらく後に現在稼働している第二世代を書きました.
特徴としては
- Python3に書き換えた
- 対象サイトへのログイン回りを自前で書き直した
mechanize
のPython3対応が見込めなかったため
- 全てDockerのエコシステムに載せた
- GitHubへpushしたらDocker HubでAutomated Build
- Dockerコンテナでデプロイ
- Fluentdにログを集約しSlackへ流すようにした
- UserStreamを監視してリアルタイムな返信機能を付加した*4
くらいで,コア部分はほぼ変更していません.エラーが出たときSlackからパッと見れるのは楽でした.ただ結局マイグレーションは手動という…
当然機能的にも大きく変わっておらず,ここでも更新分の差分をとっていません.なぜでしょうね.
さらに,まだまだ何も分かっていない初心者だった当時の自分の設計がかなり雑でもはや実装を読み解くのは容易ではなくなってしまいました. なぜか非同期処理にハマっていた節があり,どう考えても不要なところを非同期にしようとしていたりします.
完全に負の遺産ですね.
何がつらいのか
開発面のつらい話
対象サイト上のデータ
ここでは適当なデータを使います.分かる人は脳内で良い感じに補完してください.
まず,スクレイピング対象のデータ形式を示します.
科目名 | 担当者 | 曜日 | 時限 | 概要 | 詳細 | 掲載日 | 更新日 |
---|---|---|---|---|---|---|---|
講座A | A太郎 | 月 | 1 | 連絡 | 定規を持ってきてください | 2018/ 12/18 | 2018/ 12/18 |
講座B | B子 | 金 | 3 | 教室変更 | 001から002へ変更です | 2018/ 12/16 | 2018/ 12/18 |
細かい部分は省きましたが,概ねこんな感じのデータになっています.また,新しいデータの追加やデータの更新は以下の様な仕様で行われる事とします.
- 新規データはこのテーブルの一番上に追加される.
- このとき更新日と作成日は同じ値を取る
- 更新時には詳細の内容と更新日を登録する.掲載日や概要,科目の情報は変化しない.
- 同一科目の連絡が複数掲示されることもある
データの識別
データベースで管理するために.まずは各データを一意に識別する必要があります.つまり,どのデータがどのように更新されたかを識別するためには,更新の前と後でデータの対応付けができないといけないということです.
難しいのはこれらのデータそれぞれについて,実際の対象サイトが使用するデータベース上にあるはずの一意なidがどこにも示されていないため,データを識別することが容易でないということです.
そこで現在はこれらのデータを下記の様に2つのセクションに分けて考えています.
科目名 | 担当者 | 曜日 | 時限 | 概要 | 詳細 | 掲載日 | 更新日 |
---|---|---|---|---|---|---|---|
講座A | A太郎 | 月 | 1 | 連絡 | 定規を持ってきてください | 2018/ 12/18 | 2018/ 12/18 |
講座B | B子 | 金 | 3 | 教室変更 | 001から002へ変更です | 2018/ 12/16 | 2018/ 12/18 |
赤がデータの識別用,青はデータが更新されたかどうかを判別する用のセクションになります.授業の情報や初回の掲載日は変化しないと考えられるため,それらを使用して識別することにします.
これらのデータについて,各々の持つ情報を一つ一つ照合しても良いですが,ここではより簡単のため,これらのセクションをそれぞれ全て1つの文字列にまとめてハッシュ化して使用します.
同じ文字列からは常に同じハッシュが得られ,文字列が変化するとハッシュも変化するため,これらの値を比べることでデータの比較が可能になります.
実際に上記の講座Aの情報をハッシュにするとこうなります.
ハッシュ値 | |
---|---|
一意識別ハッシュ | 6351264e3a66aed80ccf31b3056a5d187d58f481 |
更新判別ハッシュ | e36c0ec693d6d6034f3e4cf1adec8736f6bece27 |
このハッシュ値を使用して以下の様な方法でデータベースの情報と照合し,更新をツイートすることにします.
更新検知の実際の流れ
さて,ハッシュを用いて簡単に判別することができることを示しましたが,これで本当にうまくいくのでしょうか?
実際には『同一の初回掲載日と概要を持つ同一科目の連絡が複数存在する場合』という悪夢のような状況が存在します(何を考えてるんだほんとに…).
この場合,同一の識別ハッシュを持つデータが複数存在することになり,どれを更新すべきなのかを判定できない状態になります.
当時のプログラム的には先に見つけた方を更新していたため,同一のデータが複数回更新され,毎回ツイートされるというバグが起きました.
これに対する良い解決手法を,僕は未だに思いついていません.そこでとんでもない雑手法によって現在は解決しています.
- 各データについて,最後にその情報を取得した日時を「データ取得日時」として記録する.
- データの更新が無かった場合にも,この「データ取得日時」だけは常に更新する
- ある識別ハッシュと一致するハッシュを持つデータの一覧を取得し以下のループを実行する
- 更新ハッシュを比較して同じ値なら「データ取得日時」を更新して終了
- そのデータの「データ取得日時」と現在時刻の差が5秒以内であれば何もせず次のループへ
- 1にも2にも該当しなければデータを更新しツイート
- 全データのうち「データ取得日時」が更新処理開始よりも前であるものは削除
5秒というのは何の根拠もなく適当に決めた値で,どう考えても実装が良くないなと思っています.
とはいえここしばらくはこれでうまくいっているようです.すごい.なんでや.
その他の考慮事項
他にもデータ処理の過程でいくつか対応を行っています.
- なぜか半角空白や全角空白,タブ文字が濫用されるので前後空白の除去と2つ以上の空白を1つの空白に置換すること
- なぜか半角カタカナや全角括弧が多用されるため,それらを正規化すること
- 詳細の内容にリンクが含まれることもあるため,それを取得すること
これを他のニュースだったり,休講だったりの内容にも適用しています.
運用面のつらい話
責任とコスト
サーバの運用費は自腹です.元々他の用途もあってサーバを稼動させているのでいいと言えばいいのですが,自分しか使っていないサービスならともかく,フォロワー数が4桁いるbotを止めるのは少し躊躇することもあり,サーバを気軽に止められないのが少しつらいです.
また,例えうまくいくことがわかっていても別のトラブルシュート*5にかかりきりになって移行が遅れたり,掃除してたらコンセント引っこ抜いちゃってサーバがダウンしたりするので*6,他人にも提供しているサービスを自宅で運用するのはあんまり嬉しくないです.
そもそも自分のために作っただけなんだから気にしなくてもいい,といえばそれはそうなんですが,現状を鑑みるにそれはあまりにも身勝手でしょう.経緯はどうあれ,それで知名度を上げようと画策したりもしたので,ある程度責任は持つつもりをしています.
モチベーションの低下
僕は今M1です.想定どおりに単位がとれれば来年からは授業がない(はず)なので,気になる情報は奨学金関連だけです*7.
正直言ってモチベーションは0です.お金にもならないですし,技術的に面白いことをちょっとやってみようかな,くらいの気持ちでどうにか前を向いています.
このbotのこれから
では最後に今後どうするかの話をして終わりにしたいと思います.
更新検知をやめる
開発のつらい話でも述べましたが,更新の検知をせず,全て新規情報が追加されたかどうかで判別すれば先に述べた問題は起きません.
2つに分けたりせず,全体でハッシュを取って識別すれば良いだけです.
内容の差分が見たかったため更新検知を目指していましたが,実際その機能がどれだけ必要かというと全くもって不要でしょう.
そもそもそこまでやっておいてdiff取る程度のことすらしていないことからも,いかに僕がその機能を必要としていないのかが窺い知れます.設計段階で気づいて欲しかった.
Pythonがつらいのでやめる
Pythonはすごく便利な言語ですが,素人のコードは絡まったミシン糸より厄介になりがちです.もう今となっては過去のコードを頑張って読み解く気力がありません.特に最近はDjangoばかり触っていたのでSQLAlchemyの使い方を忘れました.
また,Pythonは型の情報が得づらいことが読みづらいコードに拍車をかけており,最近ではtype hintsがちゃんと認知されてきましたが,当時の僕は中途半端にしか使えていません.
これらを踏まえ,Goで作り直そうと思っています.デプロイが容易であり,静的型と比較的厳格なformatterやlinterによって書くコードに一貫性を持たせることが出来ます.
結局頑張ってPythonでtype hintsを手動で付けるより,そもそも型のある言語でやったほうが良いのではないかというのが今のところの結論です.
オンプレがつらいのでやめる
Dockerコンテナ化して以降はそこまででもないですが,自宅サーバにデプロイするのはやはりあまり意味が無いなと考えていました.
最近サーバを入れ替えてGitlabのホストを辞めたこと,Jenkins等を建てたくないこと,sshのポートを開けるメリットが薄いので閉じていること,などの要因が重なりオンプレでは自動デプロイが面倒なので今後はクラウドでやりたいと思っています.
また,誰かがこれを必要としたときに簡単に使えるようになっていることが望ましいと思っているので,
- 登録・利用が簡単
- 料金体系が分かりやすい
- CI/CDが可能
- できるだけ無料でホストできる
- DBも使える*8
という要件を満たすherokuを検討しています.Google App EngineでGoを使うことも考えましたが,2nd generationへの移行がごちゃついていること,ロックインされがちであることから避けました.
新しい技術への挑戦
Goで書くとかクラウドに載せるとかではもうあんまりわくわくしないので,単なるbotではなくAPI化して,JSONではなくProtocol Bufferを使いたいと思っています.
ただherokuはgRPCに対応していない*9こと,要件的にも十分足るだろうという判断でtwirpの利用を検討しています.
が,研究と就活の狭間で身動きが取れない日々を送っているので後回しです…
一緒に開発やりたいっていう人を大募集中です.僕は一人ではもうモチベが持続しそうにありません.
卒業したら…
アカウントごと消すつもりをしています.不便になってもそれは僕のせいじゃなくて学務課のせいなので,「その公式っぽいTwitterアカウントは学祭の実況しかできないのか?」と偉い人のところへ乗り込んで説教してください.
まとめ
- クソサービスは待ってても良くならない.自分で改善していけ.
- クソサービスはデータ形式もクソ.学生に向かってなんだその半角カタカナは.
- Pythonを書く時はtype hintsを付けないと後々の自分が苦しむぞ
- Goはいいぞ
さて,あくあたん工房はこんな苦悩とは無縁の活動をしているので大変のびのびやっています.うちに入ったからこれを保守しないといけないなんていうことは全くないので,Golangをやりたい/やってる人,是非ご参加ください.Slackの #gophers
チャンネルがいつでもあなたをお待ちしています.
それでは.
*1:クソリプを飛ばしたりD進を煽ったりラーメンの判定をしない方のbotです
*3:毎回sshしてgit pullしてDBマイグレーションは自分でSQLを叩いていた.環境はpyenvで切っていたが,まだ使い方がよくわかっておらず何度もシステムのPythonから叩いて怒られた.
*4:なおこれはTwitterのUserStream廃止に伴って完全に死にました
*5:最近だとUEFI更新中に電源を落としてしまって起動すらできなくなったりしました
*6:全面的に僕が悪いので反省しています
*7:大学の就活情報をあてにしていないので…
*8:無料ではレコード数制限がありますが,削除されたものはDBから消していくようにすると1000レコードくらいで収まるのではないかと思います.
自作キーボードに手を出した
沼だってわかってはいたんだ……
↑最高に可愛い
きっかけ
Twitterを眺めてたらMint60っていうキーボードの予約が始まったっていうツイートが流れてきたのでちょっとググってみた.
なにこれ可愛い…インターン終わりの夜で疲れ果てていたので記憶が曖昧なのだけれど,気が付くとご予約完了のメールが届いていたのだ…
到着〜組み立て
参考までに今回の発注は下記の様な内容でした.
- スターターセット
- アクリルカバー:マットクリア
- 軸:Gateron Silent Red
- キーキャップ:ABS Cubic
公式のブログが非常に丁寧なこと,他の方のブログでも紹介されていることからここは割愛します*1
以下自分のツイートから抜粋.
#Mint60 きた!!!! pic.twitter.com/2KTaNsz5WE
— 院生 (@pudding_info) 2018年10月6日
後はキーをはめます #Mint60 pic.twitter.com/ZMLZ4t3WBL
— 院生 (@pudding_info) 2018年10月6日
めちゃくちゃかわいくないですか… #Mint60 pic.twitter.com/RVKBpvBj4m
— 院生 (@pudding_info) 2018年10月6日
はいかわいい〜〜優勝〜〜〜 #Mint60 pic.twitter.com/AINU3qV2g8
— 院生 (@pudding_info) 2018年10月6日
個人的注意点
ほとんどの部分ではつまずくことは無かったし,半田付けを日常的にやっているわけではない身でもそれほど難易度が高いとは感じませんでした.ゆかり屋さんに感謝.
テープLED
Mint60では裏面にテープLEDがついており,いろいろな色やパターンで光らせることが出来ます.大変かわいらしい.
ただこれを貼る列(左シフト〜Bのある列とN〜Enter下の初期ではFnになっているキーの列)の半田付け後の突き出たピンと干渉することがあるようです.これは公式にも書いてあり,この部分の突き出たピンをしっかり切るよう注意書きがされています.実際に手元でその列だけ死ぬという事案があり,確かめてみたところテープLEDを離すと正常に動作しました.
最初はテープLEDの裏紙を剥がして普通にくっつけていたのですが,どうも不安定になってしまうため,裏紙を貼り直しました.アクリルボードと基板の間で挟まれているため張り付いている必要は無いと判断してこうしたのですが,もう少し安全にしたいなら絶縁テープ等を咬ますべきでしょう.
また,そもそも最初からここはピンを切り詰める等しないとテープLED挟んだときに厚みが出過ぎてアクリルボードが少し反るようです*2.
スタビライザー
公式に組み立て方が載っています.「金属のパーツを固定する爪を持っている樹脂パーツ」と,「そうではない小さい方のパーツ」を組み合わせるときは底面に切り欠きがあるためそれが合うようにしないといけません.写真が無くて申し訳ないのですが一回失敗してしまい折れそうになりながら外しました.
また,右側キーボードの最下段のShiftキーの位置は3箇所から選べるのですが,このとき最も右側に寄せてスタビライザーを付けると,残り二つのキーはスイッチを通常と逆向きに付ける必要があります.これは基板を見ると分かるのですが,みんなSpaceキーを分割した形を好むのかあまり作例が無く戸惑ってしまいました.自分を信じて半田付け.
ファームウェアをカスタム
せっかくProMicroが載った自作キーボードを手に入れたのだから配列を自分好みにしないともったいない!というわけで自分でファームウェアをカスタムします.
環境は以下
- MacBook Pro 2017 (Touch Bar有り)
- Homebrew 1.7.6
- Docker for Mac 18.06.1-ce, build e68fc7a
- avrdude 6.3
カスタマイズ
Mint60で初めてキーカスタマイズに手を出すみたいな人が居るのかはわからないのですが下記によくまとまっていました.ただリポジトリが移転してたり2年前の記事であることから結局公式ドキュメントなどを参考にすることも多かったです.
基本的にはデフォルトのキーを入れ替える程度かとは思いますが, LT()
やALT_T()
,CTL_T()
などのような単押しと長押しで異なるキーを使用する設定を使う場合,僕はデフォルトの設定ではかなりタップの時間が厳しく,思っていたような動作が出来ませんでしたので対抗策を紹介します.
keyboards/mint60/keymaps/YOUR_KEYMAP_NAME/config.h
を編集します.
// 長押しと判定するまでの時間(ms).デフォルトは200. #undef TAPPING_TERM #define TAPPING_TERM 200 // 他のキーを押さなければ長押し判定になるタイミングでも単押しにする #define RETRO_TAPPING // 単押し判定のタイミングでも他のキーを一緒に押したら長押し判定にする #define PERMISSIVE_HOLD
このあたりは打ってみて気に食わないとか誤動作する等あれば良い感じにすればいと思います.
ビルド
[追記 2019/11/4]
QMK公式のDockerコンテナが出たのでそれに合わせて更新しました.
できるだけ環境依存を減らしたいので,Dockerコンテナでビルドします.
上記公式ドキュメントにありますが,やり方は以下の通りです.YOUR_KEYMAP_NAME
を自分のカスタムした名前に読み替えてください.
$ KEYMAP_NAME=YOUR_KEYMAP_NAME $ git clone https://github.com/qmk/qmk_firmware $ cd qmk_firmware $ cp -r keyboards/mint60/keymaps/default keyboards/mint60/keymaps/$KEYMAP_NAME # 編集する $ vim keyboards/mint60/keymaps/$KEYMAP_NAME/keymap.c $ docker run --rm -e keymap=$KEYMAP_NAME -e keyboard=mint60 -v $PWD:/qmk edasque/qmk_firmware
これで.build
以下にmint60_YOUR_KEYMAP_NAME.hex
というファイルが出来ます.
僕はqmk_firmwareをフォークしましたがこれは好みかと思います.またビルド時にそれなりに時間がかかるため,ちゃんと完了するまで待ちましょう.
フラッシュ
[追記 2019/11/4]
macOS Catalina 10.15でavrdudeでの書き込みが失敗する件についてはこちらに記述しました.
ビルドした.hexファイルを書き込みます.上記ブログを参考に,Dockerコンテナではできないそうなので*3avrdude
をインストールする
$ brew install avrdude
後は
- キーボードをUSBでPCと接続
- 裏面のリセットボタンを押す
- 7秒以内に下記コマンドを実行する
$ sudo avrdude -p atmega32u4 -c avr109 -P /dev/tty.usbmodem* -U flash:w:.build/mint60_$KEYMAP_NAME.hex
左右どちらも書き込めば完了です.
個人的に微妙な点
不満が全くないわけではないのでいくつか書きます.
スタビライザー
せっかくGateron Silent Redで通常のキーは静かなのにスタビライザーを使用しているキーではかなりカチャカチャ言います. またこれは個体差なのでしょうがたまたまEnterに使っているスタビライザーがちょっと擦れるキーキー言う音が鳴ってしまいます.そのうち潤滑油を塗ろうかな…
自作キーボードには詳しくないのですが,静かなスタビライザーというものは金を積むだけで手に入る物なんでしょうか?買えるなら欲しい…
バックスペースの位置
矢印キーがあるのは望んでいる人も多いので構わないのですが,普段HHKBを使っていた身からするとバックスペースの位置が遠いです. 親指で押せる位置に単押しバックスペースを配置していますが,今度は左上がバッククォートなのが気になってきます,右上のキーを1u二つにしてバックスラッシュとバッククォートを割り当て,バックスペースを一つ下に持ってこれると個人的には完璧だったなぁと.HHKBに慣れすぎて指がバグっているだけという気がしています.
左サイドの最下段キーの配置
右側では長いキーの位置を選択できたのに対して左では選べません.ここも親指をもうちょっと多用できると良かったなぁと思います.ただの好みという気はしますし,あんまりやる人も居なさそうです.
まとめ
機能性だけでなく見た目に大きく惹かれたため,綺麗に出来上がって大変満足です.ゆかりさんに感謝…!
身の回りでもこれを期にやってみたいみたいな声はいくつか聞こえているので再販是非頑張って頂きたい… 個人的にも今回買ったのは研究室用だったので家用にもう一台欲しい気持ちはあります.よろしくおねがいします!!!
HHKB用のパームレストが良い感じの長さなのでこれをぶった切れば…とか考えています.自作界隈の方々はパームレストどうしてるのでしょう.