iMind Developers Blog

iMind開発者ブログ

Pythonのclickでコマンドライン引数を扱う

概要

Pythonでコマンドライン引数を扱う際に便利なclickについて、ありがちなケースでの使い方についてまとめる。

バージョン情報

  • Click==7.0
  • Python 3.7.3

コマンドライン引数の取得

簡易な例として、name(string)とage(int)の2つの引数を取るコードを書いてみる。

下記のようにデコレータで引数を指定することで、関数の引数にコマンドライン引数をマッピングできる。

import click

@click.command()
@click.option('--name', help="your name")
@click.option("--age", type=int, help='your age')
def disp_person(name, age):
    message = 'your name : {}, your age : {}'
    click.echo(message.format(name, age))

if __name__ == '__main__':
    disp_person()

実行してみる。

$ python example1.py --name foo --age 30

your name : foo, your age : 30

--helpも実装される。

$ python example1.py --help

Usage: example1.py [OPTIONS]

Options:
  --name TEXT    your name
  --age INTEGER  your age
  --help         Show this message and exit.

型が違うとエラーになる。下記はintを指定しているageに文字列を渡した場合。

$ python example1.py --name foo --age bar

Usage: example1.py [OPTIONS]
Try "example1.py --help" for help.

Error: Invalid value for "--age": bar is not a valid integer

引数を指定しない場合はNoneが入る。intを指定していても未指定だとNoneで通る。

$ python example1.py

your name : None, your age : None

指定できる型については別途まとめたのでそちらを参照。

必須チェック

required=Trueを指定すると必須チェックが行われる。

@click.command()
@click.option('--name', help="your name", 
        required=True)
@click.option("--age", type=int, help='your age',
        required=True)
def disp_person(name, age):
    message = 'your name : {}, your age : {}'
    click.echo(message.format(name, age))

引数なしで実行した結果。

$ python example1.py

Usage: example1.py [OPTIONS]
Try "example1.py --help" for help.

Error: Missing option "--name".

デフォルト値の設定

defaultオプションでデフォルト値を設定する。

@click.command()
@click.option('--name', help="your name",
        default="Luigi")
@click.option("--age", type=int, help='your age',
        default=26)
def disp_person(name, age):
    message = 'your name : {}, your age : {}'
    click.echo(message.format(name, age))

引数なしで実行した結果。自動でデフォルト値が設定される。

$ python example1.py

your name : Luigi, your age : 26

envvarを指定するとデフォルト値に環境変数が入る。

下記はHOSTという環境変数を参照する。

@click.command()
@click.option('--host', envvar='HOST')
def disp(host):
    click.echo('host={}'.format(host))

HOSTを設定しつつスクリプトを実行する。YAGIという名前のホストで実行。

$ HOST=`hostname` python example1.py 

host=YAGI

envvarとdefaultが同時に指定された場合は、まずenvvarを解決してなければdefaultが使われる。

短縮名の指定

optionには複数の引数名を指定できる。

import click

@click.command()
@click.option('-n', '--name') 
def disp(name):
    click.echo('name={}'.format(name))

if __name__ == '__main__':
    disp()

-n, --name どちらでも実行可能。

$ python example1.py -n Luigi
name=Luigi

$ python example1.py --name Luigi
name=Luigi

3つ以上パラメータを付けることもできる。またその場合の名前も指定できる。

下記は -n, --name, --name2の3つの名前が指定可能で、渡される引数の名前はnameとする。

@click.command()
@click.option('-n', '--name', '--name2', 'name')
def disp(name):
    click.echo('name={}'.format(name))

実行例。--name2 を指定しているが、渡される関数にはnameとして渡される。

$ python example1.py --name2 Luigi

name=Luigi

argumentで引数指定

optionではなくargumentで指定すると --name のようなオプション指定なしで引数を受け渡す。

import click

@click.command()
@click.argument('name')
@click.argument('age', type=int)
def disp(name, age):
    message = 'your name : {}, your age : {}'
    click.echo(message.format(name, age))

if __name__ == '__main__':
    disp()

実行例

$ python example1.py Luigi 26

your name : Luigi, your age : 26

引数を配列/タプルで渡す

nargs=n を指定すると長さがnの配列として引数が渡る。nを-1にすると可変長。

nに3を指定した場合。

import click

@click.command()
@click.option('--names', nargs=3)
def disp(names):
    click.echo(names)

if __name__ == '__main__':
    disp()

3つ値を渡すとtupleで値が渡る。

$ python example1.py --names Mario Luigi Koopa
('Mario', 'Luigi', 'Koopa')

3つでないとエラーになる。

$ python example1.py --names Luigi
Error: --names option requires 3 arguments

@click.optionでnargsに-1を指定するとエラーになる。

TypeError: Options cannot have nargs < 0

@click.argumentでは nargs=-1 が指定可能。

@click.command()
@click.argument('names', nargs=-1)
def disp(names):
    click.echo(names)

可変長で値が受け取れる。

$ python example1.py Luigi Mario Koopa Peach
('Luigi', 'Mario', 'Koopa', 'Peach')

@click.option でも mutiple=True を指定すると可変長で値を渡せる。

@click.command()
@click.option('-n', '--names', multiple=True)
def disp(names):
    click.echo(names)

実行例。

$ python example1.py -n Luigi -n Mario
('Luigi', 'Mario')

プロンプトでの引数受付け

@click.option にpromptを指定するとパラメータが指定されていない場合はプロンプトで入力を受け付けるようになる。

下記はnameとpasswordの入力を求める例。passwordは非表示にして2回入力を求める。

@click.command()
@click.option('--name', prompt='name')
@click.option('--pw', prompt='password',
        hide_input=True,
        confirmation_prompt=True)
def disp(name, pw):
    click.echo('name={}, pw={}'.format(name, pw))

実行例。

$ python example1.py

name: Luigi
password: 
Repeat for confirmation: 

name=Luigi, pw=test

callbackによる引数チェックや整形

引数チェックを入れたい場合はcallbackでチェック関数を指定して、チェックに引っかかったらclick.BadParameterをraiseする。

またcallbackでは引数を任意の型に変換できる。

例として引数がaから始まる場合のみ許可するvalidate処理を書いてみる。

import click

def validate_startswith_a(ctx, param, value):
    if value and value.startswith('a'):
        return value
    else:
        raise click.BadParameter('%s is not startswith a' % value)

@click.command()
@click.option('--name', callback=validate_startswith_a)
def disp(name):
    click.echo('name={}'.format(name))

if __name__ == '__main__':
    disp()

aから始まれば許可。

$ python example1.py --name alain

name=alain

aでなければエラー。

$ python example1.py --name bobby

Usage: example1.py [OPTIONS]

Error: Invalid value for "--name": bobby is not startswith a

eagerとcallbackで特例の引数設定

--versionのような他の引数とは違った振る舞いをする場合は、is_eagerとcallbackを利用して他の関数に処理を移譲できる。

下記は --version にis_eagerを設定し、バージョン表示をさせてた後にexitさせている。 --name がrequirerdになっているのでnameが先に評価されるとエラーになってしまうが、--versionがis_eagerになっているので必ずprint_versionが先に呼び出される。

import click

def print_version(ctx, param, value):
    click.echo('example 1.0')
    ctx.exit()

@click.command()
@click.option('--name', required=True)
@click.option('--version', is_flag=True,
        is_eager=True,
        callback=print_version)
def disp(name):
    click.echo('name={}'.format(name))

if __name__ == '__main__':
    disp()

実行例

$ python example1.py --version

example 1.0

例えば--versionからis_eagerを外して--nameに付けると先にnameのrequiredが評価されてエラーになる。

@click.command()
@click.option('--name', required=True,
        is_eager=True,
        callback=validate_something)
@click.option('--version', is_flag=True,
        callback=print_version)
def disp(name):
    click.echo('name={}'.format(name))

実行例

$ python example1.py --version

Usage: example1.py [OPTIONS]
Try "example1.py --help" for help.

Error: Missing option "--name".

改定履歴

Author: Masato Watanabe, Date: 2019-05-25, 記事投稿