pythonを使っていると型チェックして欲しいなーと思う場面に時々出逢う。docopt
を使用しているときにもそう感じてしまった。引数はint
のはずなのにうっかりテキストを入れちゃって、ジャングルの奥深くに着いた時にエラーになるのは勘弁して欲しい。そこでジャングルの入り口で毎回型チェックを書くわけだが、同じことを毎回書いているといいヘルーパークラスが朧げながら思い浮かんできたんです。今日はそんなやつを紹介する。
以下はコード。
#! /usr/bin/env python3.9
# encoding: utf-8
"""{f}\n
Usage:
{f}
{f} <str> <int> <path> <list>...
{f} -h | --help
Options:
<str> str arg
<int> int arg
<path> path arg
<list> list[int] arg
-h --help Show this screen and exit.
"""
from docopt import docopt
from pathlib import Path
from typing import TypeVar, get_args
class DocArgs:
A = TypeVar("A", str, Path, int, float, bool, list)
def __init__(self, **kwargs):
self.argd = docopt(__doc__.format(f=__file__), **kwargs)
def __call__(self, value_type: A, flag: str) -> A:
"""
transformer from docopt option string to another class
:param value_type: ex) int, float32, etc
:param flag:
:return:
"""
try:
arg = value_type(self.argd[flag])
if isinstance(arg, list):
inner_type = get_args(value_type)[0]
arg = [inner_type(z) for z in arg]
return arg
except ValueError as e:
print("check your args")
raise e
def __str__(self) -> str:
return self.argd.__str__()
if __name__ == "__main__":
args = DocArgs(version='0.0.1')
print(args)
arg_str = args(str, "<str>")
arg_int = args(int, "<int>")
arg_path = args(Path, "<path>")
arg_list = args(list[int], "<list>")
print(arg_str)
print(arg_int)
print(arg_path)
print(arg_list)
使い方は簡単で、いつも
ori_args = docopt(__doc__.format(f=__file__))
としているところを
new_args = DocArgs()
とするだけだ。短くなったが、ここはコピペするところだからあんまり関係ないだろう。
引数の展開はこうだ。
arg = ori_args["<test>"] # オリジナル
arg = int(ori_args["<test>"] # 型変換するときはこう
arg = new_args(int, "<test>") # DocArgsはこう
型変換を入れた元のバージョンと文字数は変わらない(スペースは無視しよう)ので、コーディングの負担は変わらない。何が嬉しいのかというと二つある。
- 型を必ず入れないと引数を取り出せない
-
list
の中身も型変換をしてくれる
1個目は横着しないようにするというくらいだが、二つ目のlist
の中まで型変換するのは毎回内包表記で書き換えていたので、これを使うとスッキリするだろう。list
の中は全て同じ型が入ることを想定しているので、list
の中に複数の型を入れようとしてる人がいるなら(いるのか?)これは使えない。
この肝はlist
の型ヒントの中の型をとってこれるかどうかというところである。list
の型ヒントは中に別の型が入るわけだが、その取り出し方が情報がなかなか見つからなくて苦労した。list
の型はlist
だが、list[int]
のように中に型を入れると、型がtypes.GenericAlias
となる。ここから中に入れている型を取り出す方法を見つけるのに苦労したが、最終的にtyping.get_args()
でとってこれるということがわかって、ジャングルから抜け出せた。
最初は__args__
を使ってたけど、typing.get_args()
の方がいいみたい。