Python argparseの使い方

追記しました。[argparseのテストで気をつけるべきこと。](http://tech.blog.uribou.me/python-argparse-exceptions/)

***

[こちら](http://tech.blog.uribou.me/python-argparsenoshi-ifang/)に移転、更新しました。

***

# スクリプトの引数仕様

とあるスクリプトの引数の仕様を下記のようにしたい。

* srcディレクトリパスの指定は必須
* dstディレクトリパスの指定は必須ではない
* 対象とするファイルの拡張子を複数指定したい
* debug用のprint文は普段はstdoutに出したくない

といった仕様をargparseを使って実装し、--helpして表示した結果が下記。
次の章からこの実装方法を順を追ってメモしておく。

```bash
$ ./photosort.py --help
usage: photosort.py [-h] [-d [PATH_ROOT_DST]]
                    [-e SORT_FILES_EXTENTIONS [SORT_FILES_EXTENTIONS ...]]
                    [--debug]
                    path_root_src

This script is ...

positional arguments:
  path_root_src         Directory path where your taken photo files are
                        located.

optional arguments:
  -h, --help            show this help message and exit
  -d [PATH_ROOT_DST], --path-root-dst [PATH_ROOT_DST]
                        Directory path where you want to create date folder
                        and locate photo files. (default: same as source
                        directory)
  -e SORT_FILES_EXTENTIONS [SORT_FILES_EXTENTIONS ...], --sort-files-extentions SORT_FILES_EXTENTIONS [SORT_FILES_EXTENTIONS ...]
                        Extentions of file which you want to sort. (default:
                        jpg)
  --debug               debug mode if this flag is set (default: False)
```

# まずは初期化

description以外はデフォルトのままでほとんどの用途に対しては問題ないと思う。
add_help(-h/–help オプションをパーサーに追加する)等、通常使うであろうパラメータは予め有効になっている。

```py:initialize
parser = argparse.ArgumentParser(description='This script is ...')
```

# 引数の追加
## その１

デフォルトの設定のままでよいパラメータもあえて指定している。（デフォルトが何なのか、ヘルプを見ないと分からないというのも面倒なので）

> srcディレクトリパスの指定は必須

add_argumentの第一引数に、-や--を付けない名前'path_root_src'を、nargsにNoneを指定する。
これにより、-や--付オプション以外の引数がスクリプトに指定されていない場合は`too few argument`エラーとなる。
もしnargsを'?'にして、defaultやconst等のパラメータと組み合わせて指定した場合は、エラーになることなくスクリプトを実行できることになる。

```py:add_argument
parser.add_argument('path_root_src', \
        action='store', \
        nargs=None, \
        const=None, \
        default=None, \
        type=str, \
        choices=None, \
        help='Directory path where your taken photo files are located.', \
        metavar=None)
```

ここで指定した第一引数の文字列'path_root_src'は、最後の仕上げでコールする`args = parser.parse_args()`が返すNamespaceオブジェクトの属性名として使用される。
つまり、下記のように実行した場合、`args.path_root_src`に"/Users/test/Pictures"が格納される。

```bash
$ ./photosort.py /Users/test/Pictures
```

## その２

> dstディレクトリパスの指定は必須ではない

add_argumentの第一引数に２種類のオプション文字列を、nargsに'?'を指定する。
これにより、可能なら1つの引数がコマンドラインから取られ、`args = parser.parse_args()`が返すNamespaceオブジェクトの属性名'path_root_dst'として使用され、引数の値はその属性にセットされる。
オプション引数そのものが存在しない（-d自体が指定されない）場合はdefaultの値が渡される。
オプション引数が指定され、その後にコマンドライン引数が無い場合はconstの値が渡される。
下記の例の場合、どちらもNoneなのであまり意味はないが…。

```py:add_argument
parser.add_argument('-d', '--path-root-dst', \
        action='store', \
        nargs='?', \
        const=None, \
        default=None, \
        type=str, \
        choices=None, \
        help='Directory path where you want to create date folder and locate photo files. (default: same as source directory)', \
        metavar=None)
```

## その３

> 対象とするファイルの拡張子を複数指定したい

add_argumentの第一引数に２種類のオプション文字列を、nargsに'+'を指定する。
これにより、最低でも1つのコマンドライン引数が必要となり、条件を満たさない場合は`too few argument`エラーとなる。
ただし、上記エラーになるのは-eが指定されているのにコマンドライン引数が指定されていない場合のみ。
-e自体が指定されていない場合は、defaultに指定した`['jpg']`が使用され、エラーとはならない。

```py:add_argument
parser.add_argument('-e', '--sort-files-extentions', \
        action='store', \
        nargs='+', \
        const=None, \
        default=['jpg'], \
        type=str, \
        choices=None, \
        help='Extentions of file which you want to sort. (default: jpg)', \
        metavar=None)
```

## その４

> debug用のprint文は普段はstdoutに出したくない

あるオプション引数が指定された場合、単純にTrueかFalseを持っていて欲しい場合、actionに'store_true'や'store_false'を指定することで実現できる。
このスクリプトが--debug付きで実行された場合は、最後の仕上げでコールする`args = parser.parse_args()`が返すNamespaceオブジェクトの属性名`args.debug`にTrueが格納されることになる。

```py:add_argument
parser.add_argument('--debug', \
        action='store_true', \
        help='debug mode if this flag is set (default: False)')
```

# 最後の仕上げ

これまでに指定した引数をオブジェクトに変換し、Namespaceオブジェクトの属性に代入して、そのオブジェクトを返してもらう。
デフォルトで、引数文字列はsys.argvから取られるので、ここで特に引数に何か指定する必要は無い。

```py:parse_args
args = parser.parse_args()
```

# 実行結果

add_argumentで指定した名前の属性が、parse_argsが返すNamespaceオブジェクトに追加されていることが分かる。

```
(Pdb) b 112
Breakpoint 1 at /Users/dodo5522/Development/manage_media_data/photosort.py:112
(Pdb) c
> /Users/dodo5522/Development/manage_media_data/photosort.py(112)<module>()
-> args = parser.parse_args()
(Pdb) n
(Pdb) dir(args)
['__class__', '__contains__', '__delattr__', '__dict__', '__doc__', '__eq__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_get_args', '_get_kwargs', 'debug', 'path_root_dst', 'path_root_src', 'sort_files_extentions']
(Pdb) args.debug
(Pdb) print args.debug
False
(Pdb) print args.path_root_dst
/Users/dodo5522/Public
(Pdb) print args.path_root_src
/Users/dodo5522/Pictures
(Pdb) print args.sort_files_extentions
['jpg', 'png', 'mov']
(Pdb)
```