argparse-dataclassはdataclassをargparseに合う形に変換して渡してくれるもの。
https://github.com/mivade/argparse_dataclass
もともとargparseを使っていたのだが、以下の理由から引数を管理するクラスを作りたくなった。
- package化して外から使える形にする際に便利
- 引数の設定だけまとめてあると管理しやすい
本当はhydraを使ってみようと思ったのだが、そのときやりたいことからすると過剰な感じだった(dataclassとして定義さえできればOKだったので)
ということで真っ先に目についたのが名前の通りそのままのargparse-dataclassだったのだが、少し使ってみたところタイトルの通り__future__.annotations
をimportすることでエラーが出る。
例えばこちらの設定に対して
@dataclass class Config: cpu: bool = False
以下のエラー
ValueError: 'bool' is not callable
エラーを出力してる箇所はこちら
https://github.com/python/cpython/blob/v3.7.12/Lib/argparse.py#L1364
で、type
というキーで登録しているのは元を辿るとこれ
https://github.com/mivade/argparse_dataclass/blob/8051690/argparse_dataclass.py#L185
ということで
- metadataとしてtypeが定義されていればそれ
type
として使う - なければdataclassの型アノテーションとして書いたものを使う
- argparse側ではそれがcallableであることが想定されている
- が、実際はただの文字列として渡っている
今回はmetadataの定義はしてなかったので、型アノテーションとして書いたものが使われている。なので解決策としてはmetadataとして以下のように型を渡せばOK。
@dataclass class Config: cpu: bool = field(default=False, metadata=dict(type=bool))
ただ、型アノテーションとして書かれたものだとなぜうまくいかないのか気になるので追ってみる。__future__.annotations
をimportしなければ問題なく通ることから、元々は組み込み型のboolコンストラクタとして使える状態だったはず。
ということで__future__.annotations
が実際に何をしているのかを見る
https://www.python.org/dev/peps/pep-0563/
PEP 563 -- Postponed Evaluation of Annotations
Just like default values, annotations are evaluated at function definition time. This creates a number of issues for the type hinting use case:
forward references: when a type hint contains names that have not been defined yet, that definition needs to be expressed as a string literal
type hints are executed at module import time, which is not computationally free.
Postponing the evaluation of annotations solves both problems.
現状だとアノテーションの評価タイミングが関数定義の時点だがそれを遅らせるというのが趣旨。それによって定義前の段階の型へのアクセスやimport時に型評価が実行されることによる計算リソースの無駄遣いをなくせる、というメリットがある。
ではいつ評価されるようになるのかというと、
To resolve an annotation at runtime from its string form to the result of the enclosed expression, user code needs to evaluate the string.
For code that uses type hints, the typing.get_type_hints(obj, globalns=None, localns=None) function correctly evaluates expressions back from its string form.
type hintを実行時に型として扱うにはユーザ側で評価の処理を実行する必要があり、それにはtyping.get_type_hints
を使えばよい。
というわけで、 タイトルに書いたようにエラーが出てしまうのは、argparse-dataclass側でそもそも__annotations__
に対する対応が入ってないためで、適用するにはtype hintの型を評価するコードを入れる必要があるということだった。
そもそもannotations
はこちらの記事を参考にして型アノテーションをpython3.9以降と同様の形でかけるようにするという目的で入れていた。
https://future-architect.github.io/articles/20201223/
中身についてはちゃんと調べていなかったので今回はとても勉強になった。annotations
をimportすることでlist[str]
などのように型名をそのままアノテーションとして使えるようになるというのも、型として評価されなくなるために可能になることだと考えられる。