dat.blog
dat
dat
nxdataka

データ分析の世界に惹かれて30代で分析会社に転職。数学・統計学・機械学習・プログラミングと色々苦しみ&楽しみながら勉強中

コマンドライン引数(sys.argv,argparse)についての覚え書き

これは何?

先日、livedoorニュースコーパスをcsvファイル形式で取得するという記事で公開するスクリプトを書いた。
それに加え、最近ちょくちょくモジュールを自作する機会があり、それらの経験を通して学んだコマンドライン引数(sys.argv,argparse)についての覚え書き。

簡単に使えるsys.argv

sys.argvはとにかく簡単に使える。標準ライブラリのsysをインポートしてしてsys.argvを呼ぶと、実行ファイルパスとコマンドライン引数をリスト型で受け取れる。

たとえば、以下のようなpythonスクリプト実行すると…

sample_argv.py
import sys

if __name__ == "__main__":

    args = sys.argv
    print(type(args), args)

    for i, arg in enumerate(args):
        type_arg = type(arg)
        print('-----------------------')
        print('arg{:02}'.format(i))
        print('type :', type_arg)
        print('value:', arg)

こんな感じの出力になる。

python sample_argv.py hoge fuga 3
# > <class 'list'> ['sample_argv.py', 'hoge', 'fuga', '3']
# > -----------------------
# > arg00
# > type : <class 'str'>
# > value: sample_argv.py
# > -----------------------
# > arg01
# > type : <class 'str'>
# > value: hoge
# > -----------------------
# > arg02
# > type : <class 'str'>
# > value: fuga
# > -----------------------
# > arg03
# > type : <class 'str'>
# > value: 3

なので、使いたいコマンドライン引数はsys.argv[1]のように添字等の方法でアクセスできる。
ただし、入力した引数はすべて文字型となる点に留意。

argparseを使うモチベーション

sys.argvを使ってると、だんだん以下のような不満がでてくる。

  • 実行にたくさんの引数が必要なとき、引数の順番を覚えらず、間違う可能性がある
  • 位置でしか引数を管理できないので、省略可能な引数がある場合面倒になる
  • 引数の型を指定できないので、エラーチェックや型チェックが面倒になる

このあたりの不満を埋めてくれるのがargparse。こっちも標準ライブラリ。

argparseの使い方

sys.argvよりもちょっとだけ手順が多いが基本はこれだけ。

  1. parser = argparse.ArgumentParser()でパーサーを作る
  2. parser.add_argument()で引数を追加する
  3. parser.parse_args()で引数を解析する

説明は後述する。まずはざっくり使用例。

たとえば、以下のようなpythonスクリプトを…

sample_argpase.py
import argparse

if __name__ == "__main__":
    # 1.パーサーを作る
    parser = argparse.ArgumentParser(description='スクリプトの説明')

    # 2.引数を追加 ######################################################

    # 位置引数
    parser.add_argument('posi_str', help='位置引数:文字型', type=str)
    # 型の指定が可能
    parser.add_argument('posi_int', help='位置引数:整数型', type=int)
    # オプショナル引数
    parser.add_argument('--op', '-o', help='op引数', type=str, default='fuga')
    # boolの場合はactionで
    parser.add_argument('--bool', '-b', help='指定するとtrue', action='store_true')
    # list(複数の値を受け取る)場合はnargsで
    parser.add_argument('-list', '-ls', help='リスト', nargs='+', type=str)

    # ここまで引数の追加 ######################################################

    # 3.引数を解析
    args = parser.parse_args()
    # 引数へのアクセス:指定した引数名の属性としてアクセスできる
    arg_str = args.posi_str

    # 以下、確認用
    print(type(args), args)
    # __dict__で辞書型としても取り出せる
    for name, value in args.__dict__.items():
        print('-----------------------')
        print('name :', name)
        print('type :', type(value))
        print('value:', value)

--help-hをつけて実行するとヘルプメッセージが見れるし…

python sample_argparse.py -h
# > usage: sample_argparse.py [-h] [--op OP] [--bool] [-list LIST [LIST ...]]
# >                            posi_str posi_int
# > 
# > スクリプトの説明
# > 
# > positional arguments:
# >   posi_str              位置引数:文字型
# >   posi_int              位置引数:整数型
# > 
# > optional arguments:
# >   -h, --help            show this help message and exit
# >   --op OP, -o OP        op引数
# >   --bool, -b            指定するとtrue
# >   -list LIST [LIST ...], -ls LIST [LIST ...]
# >                         リスト

引数を与えて実行するとこんな感じの出力になる。

python sample_argparse.py hoge 2 -ls piyo foo bar
# > <class 'argparse.Namespace'> Namespace(bool=False, list=['piyo', 'foo', 'bar'], op='fuga', posi_int=2, posi_str='hoge')
# > -----------------------
# > name : posi_str
# > type : <class 'str'>
# > value: hoge
# > -----------------------
# > name : posi_int
# > type : <class 'int'>
# > value: 2
# > -----------------------
# > name : op
# > type : <class 'str'>
# > value: fuga
# > -----------------------
# > name : bool
# > type : <class 'bool'>
# > value: False
# > -----------------------
# > name : list
# > type : <class 'list'>
# > value: ['piyo', 'foo', 'bar']

名前付きでコマンドライン引数にアクセスでき、型も指定通りだ。だいぶスマートになった。

以下、各ステップの説明。

1.パーサーを作る

parser = argparse.ArgumentParser()の部分。
ArgumentParserの引数にdescription='desc'として解説文を書いた場合、この解説文をヘルプメッセージで確認できる。

2.引数を追加する

parser.add_argument()で引数を追加していく。このステップがargparseの肝の部分
ここで追加した位置引数を省略したり、定義と違う型の引数を受け取るとエラーを起こすようになってくれる。
さらに引数毎にヘルプメッセージで表示する説明文をつけられる。

このadd_argumentメソッドが取る引数は以下の通り。(よく使うもののみ紹介)

  • *name_or_flags
    'posi_str'のように--始まりでない名前を指定すると位置引数に、'--op'のように--始まりの名前を指定するとオプショナル引数になる。 また、'--bool', '-b'のように-で始まる短縮名を登録しておくことが可能。
  • help
    ヘルプメッセージで表示される引数の説明を指定できる。
  • type
    type=intのように引数の型を指定できる。
  • default
    引数を省略した場合のデフォルト値を指定できる。
  • action
    bool型の場合はこれにaction='store_true'を指定する1。このフラグを立てた場合はTrue、省略した場合はFalseの値が得られる。
  • nargs
    リストを受け取りたいときにnargs='+'を指定する2。この場合、typeはリスト内の要素の型を指定していることになる。

ちなみに先日公開したスクリプトでは文字型のオプショナル引数しか使っていない。

3.引数を解析する

args = parser.parse_args()で引数を取得してargsの属性にアクセスするだけ。たとえばarg.posi_strのようにアクセスできる。属性名はadd_argment*name_or_flagsで定義した名前。
ただし、オプショナル引数は短縮名でなく正式名でないとアクセスできない。たとえばargs.bではアクセスできず、args.boolでないといけない。

ちなみにこのparse.parse_args()の返り値argsはNamespaceオブジェクト。このオブジェクトがコマンドライン引数の値を保持した名前空間を持っているイメージで良さそう。
さらにちなみに、こいつの__dict__属性にアクセスすると、名前空間内で定義された変数(コマンドライン引数)に辞書型でアクセスできる。

感想

データ分析職(非開発職)だとPython使っていてもJupyter notebookでpandasだとか機械学習のフレームワークだとかを使ってばかりで、標準ライブラリの理解は疎かになりがちです。改めてまとめてみると勉強になりますね。

argparseはコマンドライン引数からbool値やリストを受け取る設定をするときにちょっとした落とし穴(脚注参照)があって、最初はみごとにひっかかりましたが、慣れたらヘルプメッセージやオプション引数がすごく便利でした。
ある程度コアとなる部分だけの覚え書きにしましたが、もっと詳細な設定が必要であれば、適宜公式ドキュメントを参照するようにします。あとチュートリアルも結構わかりやすかったです。


  1. type=boolとしてしまうと意図通りには動かないので注意。参考:Pythonのargparseでブール値を扱うときは注意が必要

  2. type=listとしてしまうと意図通りには動かないので注意。参考:How can I pass a list as a command-line argument with argparse?