概要
Pythonでコマンドライン引数を扱う際に便利なclickについて、利用可能なTypeについてまとめる。
バージョン情報
- Click==7.0
- Python 3.7.3
指定可能なタイプ
下記のTypeが利用できる。
- string
- int
- float
- bool
- click.UUID
- click.File
- click.Path
- click.Choice
- click.IntRange
- click.FloatRange
- click.DateTime
- 独自拡張
int
intは整数のみ受け付ける。下記はintのコマンドライン引数ageを受け取るコード。optionでtype=intを指定している。
import click @click.command() @click.option("--age", type=int) def disp(age): click.echo('age={}'.format(age)) if __name__ == '__main__': disp()
数字を渡した場合は正常に処理が実行される。
$ python example1.py --age 1 age=1
requiredでなければNoneも許可される。この挙動はint以外の他のTypeでも同じ。
下記は --age を指定しなかった場合。Noneが渡されている。
$ python example1.py age=None
Intに変換できない文字列やfloatは受け付けない。
$ python example1.py --age 10.3 Usage: example1.py [OPTIONS] Try "example1.py --help" for help. Error: Invalid value for "--age": 10.3 is not a valid integer
int(value) を通る文字列であれば許可される。
https://github.com/pallets/click/blob/7.0/click/types.py#L244
float
floatは数値のみ受け付ける。下記はscoreという名前でfloatの引数を受け取っている。
import click @click.command() @click.option("--score", type=float) def disp(score): click.echo('score={}'.format(score)) if __name__ == '__main__': disp()
小数点を通す以外はintと同じ。
$ python example1.py --score 0.3 score=0.3
変換処理は float(value) で行っている。
https://github.com/pallets/click/blob/7.0/click/types.py#L296
bool
boolはTrue/Falseを受け取る。
Trueとして扱われる値は下記(大文字小文字は無視)。
- true
- t
- 1
- yes
- y
Falseとして扱われる値は下記。
- false
- f
- 0
- no
- n
import click @click.command() @click.option("--flag", type=bool) def disp(flag): click.echo('flag={}'.format(flag)) if __name__ == '__main__': disp()
Yesを指定して実行。
$ python example1.py --flag Yes flag=True
「はい」を指定して実行する。もちろんエラーになる。
$ python example1.py --flag はい Usage: example1.py [OPTIONS] Try "example1.py --help" for help. Error: Invalid value for "--flag": はい is not a valid boolean
booleanに変換する処理は下記あたりを参照。
https://github.com/pallets/click/blob/7.0/click/types.py#L350
booleanを引数に取りたい時はis_flag を使う方が楽かもしれない。
import click @click.command() @click.option('--flag', is_flag=True) def disp(flag): click.echo('flag={}'.format(flag)) if __name__ == '__main__': disp()
引数が指定されていればTrue、されていなければFalseになる。
$ python example1.py --flag flag=True $ python example1.py flag=False
--flg/--no-flg のようにTrue用とFalse用双方の引数を用意する方法もある。
@click.command() @click.option('--flg/--no-flg') def hello(flg): click.echo('flg={}'.format(flg))
--flgを指定すればTrue、--no-flgを指定すればFalseが返る。
$ python example1.py --flg flg=True $ python example1.py --no-flg flg=False
click.UUID
click.UUIDは名前の通りUUIDを引数に取る。
事前準備として引数に渡せるvalidなuuidを生成。
$ python -c "import uuid; print(uuid.uuid4())"
uuidを引数に取るコード。
import click @click.command() @click.option("--uuid", type=click.UUID) def disp(uuid): click.echo('uuid={}'.format(uuid)) if __name__ == '__main__': disp()
uuidを引数に渡して実行すると正常に通る。
$ python example1.py --uuid 1b3b9c7c-71d8-4c6d-83bb-068055891f7d uuid=1b3b9c7c-71d8-4c6d-83bb-068055891f7d
uuidの形式に合っているかチェックが行われているので、例えば末尾の1文字を削って実行すると形式が合ってないぞとエラーになる。
$ python example1.py --uuid 1b3b9c7c-71d8-4c6d-83bb-068055891f7 Usage: example1.py [OPTIONS] Try "example1.py --help" for help. Error: Invalid value for "--uuid": 1b3b9c7c-71d8-4c6d-83bb-068055891f7 is not a valid UUID value
uuid.UUID(value) が通る文字列である必要がある。
https://github.com/pallets/click/blob/7.0/click/types.py#L369
click.File
click.Fileは指定されたパスのFile I/Oを返す。
事前に適当なファイルを作っておく。
$ echo 'foobar' > temp.txt
引数で指定されたパスのファイルを読んで標準出力するコード。
import click @click.command() @click.option("--file", type=click.File('rt')) def disp(file): click.echo('file={}'.format(file.read())) if __name__ == '__main__': disp()
これでファイルの中身が取れる。
$ python example1.py --file temp.txt file=foobar
存在しないパスの場合はエラーになる。
$ python example1.py --file hoge.txt Usage: example1.py [OPTIONS] Try "example1.py --help" for help. Error: Invalid value for "--file": Could not open file: hoge.txt: No such file or directory
modeにwを指定して指定ファイルに出力してみる。
import click @click.command() @click.option("--file", type=click.File('wt')) def disp(file): file.write('foobarbaz') if __name__ == '__main__': disp()
hoge.txtに出力。
$ python example1.py --file hoge.txt $ cat hoge.txt foobarbaz
click.Fileの引数にはmode以外にもencoding, lazy(評価されてから開く), atomic(write時にtemporaryに書いて終わったらmvする)などが指定できる。
https://github.com/pallets/click/blob/7.0/click/types.py#L406
click.Path
click.Pathはファイルやディレクトリのパスを指定する。下記はexists=Trueを指定して存在チェックも行っている。
import click @click.command() @click.option("--path", type=click.Path(exists=True)) def disp(path): click.echo('path={}'.format(path)) if __name__ == '__main__': disp()
存在するパスを引数に入れれば通る。
$ python example1.py --path temp.txt path=temp.txt
存在しないパスを入れるとチェックに引っかかる。
$ python example1.py --path fuga.txt Usage: example1.py [OPTIONS] Try "example1.py --help" for help. Error: Invalid value for "--path": Path "fuga.txt" does not exist.
click.Fileにはfile_okay / dir_okayという引数が用意されている。たとえば dir_okay=False にしておくと、指定パスがディレクトリであればエラーになる。
import click @click.command() @click.option("--path", type=click.Path(dir_okay=False)) def disp(path): click.echo('path={}'.format(path)) if __name__ == '__main__': disp()
ディレクトリを生成してそのパスを指定すると、dir_okay=Falseなのでエラーになる。
$ mkdir dir $ python example1.py --path dir Usage: example1.py [OPTIONS] Try "example1.py --help" for help. Error: Invalid value for "--path": File "dir" is a directory.
resolve_pathを指定すると事前にos.path.realpathでsymlink等を展開してくれる。
symlinkを貼って準備。
$ ln -s /tmp root_temp
resolve_path=Trueに設定。それからfile_okay=Falseも指定しておく必要がある。
import click @click.command() @click.option("--path", type=click.Path(file_okay=False, resolve_path=True)) def disp(path): click.echo('path={}'.format(path)) if __name__ == '__main__': disp()
resolve_path周りの条件はここらへん。
https://github.com/pallets/click/blob/7.0/click/types.py#L523
その他、readable/writeableという引数で読込み/書込みが可能かチェックする引数も用意されている。
click.Choice
click.Choiceは指定された値の中から選択する。下記はAかBのいずれかを選択する例。
import click @click.command() @click.option("--choice", type=click.Choice(['A', 'B'])) def disp(choice): click.echo('choice={}'.format(choice)) if __name__ == '__main__': disp()
Aを選択。
$ python example1.py --choice A choice=A
引数に指定した以外の値を引数に入れるとエラー。
$ python example1.py --choice C Usage: example1.py [OPTIONS] Try "example1.py --help" for help. Error: Invalid value for "--choice": invalid choice: C. (choose from A, B)
Choiceの引数はStringを期待されていて、Intを指定すると下記のようなエラーになる。
TypeError: sequence item 0: expected str instance, int found
引数に case_sensitive=False を指定すると大文字小文字どちらでも通るようになる。
@click.command() @click.option("--choice", type=click.Choice(['A', 'B'], case_sensitive=False)) def disp(choice): click.echo('choice={}'.format(choice))
click.IntRange
click.IntRangeは範囲指定付きのInt。下記は0〜120を許可する例。
import click @click.command() @click.option("--age", type=click.IntRange(0, 120)) def disp(age): click.echo('age={}'.format(age)) if __name__ == '__main__': disp()
130を指定すると範囲外なのでエラる。
$ python example1.py --age 130 Usage: example1.py [OPTIONS] Try "example1.py --help" for help. Error: Invalid value for "--age": 130 is not in the valid range of 0 to 120.
clamp=Trueを指定すると、範囲外の場合は許容される上限or下限の値に変換される。
import click @click.command() @click.option("--age", type=click.IntRange(0, 120, clamp=True)) def disp(age): click.echo('age={}'.format(age))
clamp=Trueで引数130を渡した例。エラーにならずに120に変換される。
$ python example1.py --age 130 age=120
下限以下の値であれば下限に変換される。
$ python example1.py --age -5 age=0
click.FloatRange
FlaotRangeはIntRangeのfloat版。
import click @click.command() @click.option("--score", type=click.FloatRange(0.0, 1.0) def disp(score): click.echo('score={}'.format(score)) if __name__ == '__main__': disp()
動作はIntRangeと同じ。clampも指定できる。
click.DateTime
click.DateTimeは日付指定。
デフォルトでは下記の3つのフォーマットを想定して順にパースを試す。
- '%Y-%m-%d',
- '%Y-%m-%dT%H:%M:%S',
- '%Y-%m-%d %H:%M:%S'
import click @click.command() @click.option("--date", type=click.DateTime()) def disp(date): click.echo('date={}'.format(date)) if __name__ == '__main__': disp()
フォーマットに合う形式で引数を送るとdatetime.datetime型で引数に渡される。
$ python example1.py --date 2019-06-23 date=2019-06-23 00:00:00
フォーマットが合わないとエラー。
$ python example1.py --date 20190623 Usage: example1.py [OPTIONS] Error: Invalid value for "--date": invalid datetime format: 20190623. (choose from %Y-%m-%d, %Y-%m-%dT%H:%M:%S, %Y-%m-%d %H:%M:%S)
引数に日付フォーマットを渡せば、指定した型でパースしてくれる。フォーマットは複数渡せる。
@click.command() @click.option("--date", type=click.DateTime(['%Y%m%d', '%Y/%m/%d'])) def disp(date): print(date.__class__) click.echo('date={}'.format(date))
typeをtupleで複数指定
typeは複数指定することができる。
下記はstringとintの2つを引数に取る指定。
import click @click.command() @click.option("--item", type=(str, int)) def disp(item): click.echo('item={}'.format(item)) if __name__ == '__main__': disp()
引数を2つ指定するとtupleで渡される。
$ python example1.py --item foo 3 item=('foo', 3)
1つしか渡さないとエラーになる。
$ python example1.py --item foo Error: --item option requires 2 arguments
引数を指定しない場合、Noneが渡るのではなくエラーになる。
$ python example1.py --item foo TypeError: It would appear that nargs is set to conflict with the composite type arity.
https://github.com/pallets/click/blob/7.0/click/types.py#L589
同じ型を配列で渡したい場合は、Tupleを使わず multiple=True を指定する。
import click @click.command() @click.option("--items", type=str, multiple=True) def disp(items): click.echo('items={}'.format(items))
実行時はこんな感じ。
$ python example1.py --items a --items b items=('a', 'b')
独自拡張Type
click.ParamTypeを継承して独自にTypeを生成できる。
例として正規表現を引数に取ってチェックしてから文字列を引数として渡す RegexParam というクラスを作ってみる。
コンストラクタで正規表現のパターンを取って、convertで正規表現にマッチすればvalueをそのままreturnし、マッチしなければfailを実行している。
import click import re class RegexParam(click.ParamType): name = 'regex param' def __init__(self, pattern): self.pattern = pattern def convert(self, value, param, ctx): if re.match(self.pattern, value): return value else: self.fail('%s is not a valid pattern' % value) def __repr__(self): return 'RegexParam(%r)' % (self.pattern)
作成したRegexParamを使って引数を指定する例。0-9の文字のみ許可するパターンを指定している。
@click.command() @click.option("--num", type=RegexParam('^[0-9]+$')) def disp(num): click.echo('num={}'.format(num)) if __name__ == '__main__': disp()
実行例。数値を入力すると正常に実行される。
$ python example1.py --num 100 num=100
数値以外を入力するとエラー。
$ python example1.py --num foo Usage: example1.py [OPTIONS] Try "example1.py --help" for help. Error: Invalid value for "--num": foo is not a valid pattern
独自Paramはいろいろ作っておくと便利で、例えばもjsonをパラメータに取るとか
import click import json class JsonParam(click.ParamType): name = 'json param' def convert(self, value, param, ctx): try: return json.loads(value) except: self.fail('%s is not a valid json format' % value) def __repr__(self): return 'JsonParam()'
カンマ区切りで値を渡して配列に変換するとか
class CsvParam(click.ParamType): name = 'csv param' def convert(self, value, param, ctx): return value.split(',') def __repr__(self): return 'CsvParam()'
使いそうな形式はいろいろ考えられる。
改定履歴
Author: Masato Watanabe, Date: 2019-05-25, 記事投稿