0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

docopt (python)で型チェックしよう

Last updated at Posted at 2021-08-21

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()の方がいいみたい。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?