Skip to content
This repository has been archived by the owner on Jan 8, 2019. It is now read-only.

Latest commit

 

History

History
290 lines (197 loc) · 15.3 KB

4.rst

File metadata and controls

290 lines (197 loc) · 15.3 KB

4部 Pillowで「明日使えるスクリプト」を作る

4部ではPillowを使って日常でも使える便利なスクリプトを作っていきます。 3部までで学んだPythonの基本的な使い方、ライブラリーの使い方を活かして便利なプログラムを完成させましょう。

4部で作るもの

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

ステップ1: 引数でファイルを受け取る

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 を使うとより高度な引数の指定が簡単に書けます。

ステップ2: ディレクトリーを探索して変換する

ディレクトリーを対象にすべての画像ファイルを変換するスクリプトを作りましょう。 例えば、画像をたくさん入れているディレクトリーがあって、その中にも「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

階層化されたディレクトリーの下まで変換されています。

ステップ3: 1つのコマンドとして完成させる

最後のステップとして、今まで作ってきたものをコマンドとして完成させましょう。 ここまでくればかなり立派なスクリプトです。少し難しいですが、最後のステップとしてぜひトライしてみてください。

ステップ1、ステップ2でコマンドを作ってきましたが問題があります。

  • resize_image.pytree_resize_image.py 内で実装が何度も重複している
  • 画像の指定方法によって別のスクリプトを使う必要がある
  • 縮小比率を指定できない

こんなコマンドを考えてみましょう。

画像の縮小:

$ thumbnailer ./images/2016.png

縮小比率を指定:

$ thumbnailer -s=0.3 ./images/2016.png

ディレクトリー指定の縮小:

$ thumbnailer -r ./images/

またいつでも使いやすいようにヘルプも表示できるようにしましょう:

$ thumbnailer --help

codes/4/thumbnailer/ 以下のPythonファイルを写経しましょう。 写経の順番は以下が分かりやすいと思います。

  1. thumbnailer/main.py
  2. thumbnailer/pathutil.py
  3. thumbnailer/thumbnail.py
  4. 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でパッケージング

ファイルの分割とimport

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ファイル間の依存関係を考えてわける

mainpathutil をインポートしているとき、「mainpathuitl に依存している」といいます。 thumbnailer の依存関係は以下のようになっています:

main => pathutil
main => thumbnail => pathutil

この依存関係がグチャグチャにならないようにモジュールをわけて、関数を中に入れるのがポイントです。 上の依存関係を見ると以下のことが分かります

  • 最も単純で汎用的な pathutil が一番他から依存されている
  • 入出力を扱う main が一番他に依存している
  • 今回一番やりたい「サムネイル化」の処理は thumbnail として中心にある

argparseで引数の処理

main.py 内の main 関数で argparse.ArgumentParser を使って引数の処理をしています。 argparseを使うときの流れとしては

  1. parser を作る
  2. parser.add_argument で引数を指定
  3. args = parser.parse_args() でコマンドで入力した値を取得、解析
  4. 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() メソッド から探して使ってみましょう。

setup.pyでパッケージング

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 関数を呼んでね、と指定しています

main.py には if __name__ == "__main__": は書いていませんが、 setup.pyconsole_scripts から指定しているので不要です。

パッケージングにはその他たくさんの情報を指定できます。 詳しくは以下を参考にしてください。

エクストラステップ1: 複数のサブコマンドを統一

thumbnailer はできましたが、他にも PNG=>JPG 変換のようなよく使うコマンドを一緒にまとめておきたいです。 「縮小する」や「形式変換する」といった個々の処理は「サブコマンド」として、 imager というコマンド内にすべて持つようにしましょう:

imager thumbnail ./images/2016.png

変換のコマンドも別のサブコマンドとして用意します:

imager convert ./images/2016.png jpg

thumbnailer で作った -r オプションは convert の場合に使えても便利そうです。

コマンドの仕様をまとめて、作ってみましょう。 argparse サブコマンド を参考にすると良いでしょう。