iMind Developers Blog

iMind開発者ブログ

PythonのclickのType一覧

概要

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, 記事投稿