4部ではPillowを使って日常でも使える便利なスクリプトを作っていきます。 3部までで学んだPythonの基本的な使い方、ライブラリーの使い方を活かして便利なプログラムを完成させましょう。
3部までで画像の縮小スクリプト、画像のPNG=>JPEG変換スクリプトなどは作れました。 ですが2部、3部のスクリプトでは毎度対象のファイル名を書き換える必要があったりして不便です:
- 対象のファイルを指定するためプログラムを修正する必要がある
- 毎度Pythonファイルを指定する必要がある(簡潔なコマンドとして使えない)
4部ではこの面倒な部分を直して、より日常使いやすいようなスクリプトにしましょう。 最初のステップとしてファイルを外部から指定できるようにします:
$ python resize_image.py ./images/2016.png ./images/2016_thumb.png
次に指定したディレクトリー以下を探索して、指定された形式のすべての画像を変換するようにしましょう:
$ python tree_resize_image.py ./images/2016.png ./images/2016_thumb.png ./images/2015_thumb.png ./images/2014_thumb.png
最後の目標としてスクリプトを1つのコマンドとしてまとめ、インストールできるようにしましょう:
$ thumbnailer -s=0.5 ./images/2016.png ./images/2016_thumb.png
codes/4/resize_image.py
を手元に写経しましょう。
写経できたら以下のコマンドを実行して画像が生成されるか確認しましょう:
$ python resize_image.py ./images/2016.png ./images/2016_thumb.png
add_file_suffix
関数と main
関数に処理を分けています。
add_file_suffix
は「ファイルパスにサフィックスをつける関数」です。この処理は入出力のハッキリした処理だったので別の関数にしています。
例えばこの関数は今回取り組んでいる resize_image.py
ではない場所でも使えますし、「画像の変換スクリプト」ではない題材でも汎用的に使える関数です。
関数に分離する利点はいくつかあります。処理が見やすくなりますし、他のファイルで使いたくなった場合に再利用しやすくなります。
if __name__ == "__main__":
のif文は、このファイルがスクリプトとして実行されているかをチェックするためにつけています。
Pythonファイル内に直接処理を書くと、この resize_image
を自分で import
した場合も実行されてしまいます。
例えば2章までの resize_image.py
は以下のように import
文を通しても実行できてしまいます:
$ python >>> import resize_image
if __name__ == "__main__":
をつけた場合は python resize_image.py
として実行しないと実行されなくなります。
sys.argv
からスクリプト呼び出し時の引数が取り出せます。
例えば python resize_image.py ./images/2016.png
と指定した場合
sys.argv
は ['resize_image.py', './images/2016.png']
というリストになります。
第1要素の 'resize_image.py'
は対象のファイル名なので不要です。2要素を取るため sys.argv[1]
としています。
この sys.argv
を使った引数の受け取り方法は一番愚直な方法です。
ステップ3では標準ライブラリー argparse
を使うとより高度な引数の指定が簡単に書けます。
ディレクトリーを対象にすべての画像ファイルを変換するスクリプトを作りましょう。 例えば、画像をたくさん入れているディレクトリーがあって、その中にも「16年の長野旅行」や「15年の箱根旅行」のようなディレクトリーがあったときに 逐一ファイルを指定するのは面倒です。ディレクトリーの下のディレクトリーも掘り下げて全て変換するスクリプトがあれば便利ですね。
codes/4/tree_resize_image.py
を手元に写経しましょう。
写経できたら以下のコマンドを実行して画像が生成されるか確認しましょう:
$ python tree_resize_image.py ./images jpg ./images/2016_thumb.jpg ./images/trip/alps_thumb.jpg ./images/trip/sky_thumb.jpg
階層化されたディレクトリーの下まで変換されています。
最後のステップとして、今まで作ってきたものをコマンドとして完成させましょう。 ここまでくればかなり立派なスクリプトです。少し難しいですが、最後のステップとしてぜひトライしてみてください。
ステップ1、ステップ2でコマンドを作ってきましたが問題があります。
resize_image.py
とtree_resize_image.py
内で実装が何度も重複している- 画像の指定方法によって別のスクリプトを使う必要がある
- 縮小比率を指定できない
こんなコマンドを考えてみましょう。
画像の縮小:
$ thumbnailer ./images/2016.png
縮小比率を指定:
$ thumbnailer -s=0.3 ./images/2016.png
ディレクトリー指定の縮小:
$ thumbnailer -r ./images/
またいつでも使いやすいようにヘルプも表示できるようにしましょう:
$ thumbnailer --help
codes/4/thumbnailer/
以下のPythonファイルを写経しましょう。
写経の順番は以下が分かりやすいと思います。
thumbnailer/main.py
thumbnailer/pathutil.py
thumbnailer/thumbnail.py
setup.py
codes/4/thumbnailer
ディレクトリー内 (setup.py
があるディレクトリー) で以下のコマンドを実行しましょう:
$ pip install -e .
このコマンドは、このディレクトリー (.
) をパッケージとしてインストールしています。
今までの Pillow
はWeb上のPyPIからインストールしましたが、 .
の用にディレクトリーを指定した場合は手元のディレクトリーからインストールします。
Note
-e
を指定して「編集可能な」状態でインストールしています。
pip install .
でも動作しますが、この場合 thumbnailer/ 以下のPythonファイルを編集してもインストールした
コードには影響しなくなってしまいます。
-e
オプションをつけることでインストールした thumbnailer
コマンドにも変更が影響します。
インストールできたら上記の thumbnailer
コマンドを一通り試してみましょう。
前回からかなり形式が変わり、新しい要素もいくつかでてきました。 ポイントとしては以下3つです。少し複雑ですが一つひとつ見ていきましょう。
- ファイルの分割とimport
- argparseで引数の処理
- setup.pyでパッケージング
resize_image_.py
などは1つのファイルで完結していましたが、今回はファイルを3つに分割しています。
pathutil.py
: ファイルパスなどを便利に扱う関数をまとめていますthumbnail.py
: 画像のサムネイルを作成する処理をまとめていますmain.py
: 一番中心になる処理です。他の関数やライブラリーをインポートして、引数の処理をして呼び出しています
main.py
を見るとファイル先頭に from .pathutil import dir_to_files
とあります。
これは「同じ階層 (.
) の pathutil
Pythonファイルから dir_to_files
をインポート」という指定です。
このインポートのおかげで main.py
ファイル内で dir_to_files
関数が使えるようになっています。
この書き方は from PIL import Image
としたのと同じです。インストールされたパッケージからインポートするのでなく、同階層の自分のPythonファイルからインポートしているのが違います。
Pythonファイルは中に入れる関数の役割、意味ごとに分割しています。 分かりやすいように自由に分けて問題ありませんが、基準としてはいくつかあります
- 関数たちの役割ごとにモジュールにわける
- Pythonファイル間の依存関係を考えてわける
main
が pathutil
をインポートしているとき、「main
は pathuitl
に依存している」といいます。
thumbnailer
の依存関係は以下のようになっています:
main => pathutil main => thumbnail => pathutil
この依存関係がグチャグチャにならないようにモジュールをわけて、関数を中に入れるのがポイントです。 上の依存関係を見ると以下のことが分かります
- 最も単純で汎用的な
pathutil
が一番他から依存されている - 入出力を扱う
main
が一番他に依存している - 今回一番やりたい「サムネイル化」の処理は
thumbnail
として中心にある
main.py
内の main
関数で argparse.ArgumentParser
を使って引数の処理をしています。
argparseを使うときの流れとしては
parser
を作るparser.add_argument
で引数を指定args = parser.parse_args()
でコマンドで入力した値を取得、解析args.size
のようにadd_argument
で指定した値を取得
main.py
に書かれた .add_argument()
の意味を上から解説します
.add_argument()
で引数を追加しています.add_argument('paths')
で位置引数を追加しています$ thumbnailer -s=0.5 foo.jpg
とコマンドで指定する場合のfoo.jpg
が位置引数ですnargs='+'
と書いて、1つ以上の値を受け取ると指定できます。$ thumbnailer foo.jp bar.jp ...
help="..."
で引数の意味を指定。ヘルプ表示に使われます
.add_argument('-s', '--size')
でキーワード引数を追加しています。$ thumbnailer -s=0.5
や$ thumbnailer --size=0.5
の-s
がキーワード引数です- 便利なように
-s
と--size
両方で解釈できるようにしています type=float
と指定して、 float型として扱うことを指定していますdefault=0.5
と指定して、キーワード引数が場合に 0.5 にするよう指定しています
.add_argument(..., action='store_true')
と指定して、このキーワード引数が指定された場合値がTrueになるように指定しています。- $ thumbnailer -r とした場合、 args.recursive は True になります
argparse.ArgumentParser
を使うと自動でヘルプ表示が作成されます。
--help
キーワード引数が指定された場合にヘルプを表示してくれます:
$ thumbnailer --help
他にも add_argument
には色々な機能があります。
試してみたいことがあれば公式ドキュメントの add_argument() メソッド から探して使ってみましょう。
pipでインストールして、 thumbnailer
コマンドとして登録できるように setup.py
ファイルを書いています。
setup.py
ファイルはPythonを「パッケージ」としてまとめる方法が記述されています。パッケージにすることで他の人や環境でも再利用しやすくなります。
例えば Pillow
にもかなり複雑ですが setup.py があります 。
Note
thumbnailerコマンドもPillowも同じ方法でPythonパッケージとして作られています。
このパッケージをPyPIに登録、アップロードすることで全世界の人が pip install Pillow
のような簡単なコマンドでインストールできるようになります。
今作ったthumbnailerももちろんPyPIに登録できます。
登録の仕方はこのテキストでは扱わないので PyPIデビュー2015 を参考にすると良いでしょう。
setup.py
の最小限の書き方は簡単です。
from setuptools import setup
setup(
name='thumbnailer',
version='0.1.0',
packages=['thumbnailer'],
entry_points={
'console_scripts': ['thumbnailer=thumbnailer.main:main'],
},
)
- name: パッケージ名。
Pillow
のような名前です - version: バージョン番号です。新しいバージョンのパッケージが古いもので上書きされないように書きます。今回はテキトウです。
- packages: パッケージに含むディレクトリーです
- entry_points:
console_scripts
を指定してコマンドとして呼び出せるように指定します - thumbnailer=: コマンドの名前を thumbnailer としています
- thumbnailer.main:main: thumbnailer.main 以下の main 関数を呼んでね、と指定しています
- entry_points:
main.py
には if __name__ == "__main__":
は書いていませんが、
setup.py
の console_scripts
から指定しているので不要です。
パッケージングにはその他たくさんの情報を指定できます。 詳しくは以下を参考にしてください。
thumbnailer
はできましたが、他にも PNG=>JPG 変換のようなよく使うコマンドを一緒にまとめておきたいです。
「縮小する」や「形式変換する」といった個々の処理は「サブコマンド」として、 imager
というコマンド内にすべて持つようにしましょう:
imager thumbnail ./images/2016.png
変換のコマンドも別のサブコマンドとして用意します:
imager convert ./images/2016.png jpg
thumbnailer
で作った -r
オプションは convert
の場合に使えても便利そうです。
コマンドの仕様をまとめて、作ってみましょう。
argparse
サブコマンド を参考にすると良いでしょう。