argparseのtype引数を型指定だと誤解していた - 実際は変換関数の指定
argparseのtype引数を型指定だと誤解していた - 実際は変換関数の指定
Python の argparse モジュールを使っていて、add_argument メソッドの type 引数について誤解していたことに気づいた。
これまで type 引数は単純に引数の型を指定するものだと思っていた。しかし実際には、この引数はコマンドライン引数を指定された型に変換するための関数を指定するものだった。CLI から渡された引数は常に文字列として受け取られ、type 引数で指定された関数を通じて変換される仕組みになっているようだ。
将来同じような誤解をしないよう、またすぐに思い出せるように記録を残しておく。
なぜこの誤解をしていたか
type 引数について、int や float は指定できるが dict や list などは指定すべきでないということは知っていた。しかし、単にそういうものだと覚えているだけで、なぜそうなるのかを理解していなかった。よく考えてみると、もし入力の型を指定するだけなら dict や list なども指定できて良さそうである。
実際の動作を確認してみる
例えば、以下のように type=int を指定した場合を見てみる。
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--number', type=int)
args = parser.parse_args()
print(f"Type of args.number: {type(args.number)}")
python main.py --number 42
# Type of args.number: <class 'int'>
コマンドラインから --number 42 のように引数を渡すと、args.number は int 型 の 42 になる。
一方で、type 引数を指定しなかった場合はどうなるか。
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--number')
args = parser.parse_args()
print(f"Type of args.number: {type(args.number)}")
python main.py --number 42
# Type of args.number: <class 'str'>
今度は args.number が str 型 の '42' になった。
カスタム関数で動作を理解する
type 引数が変換関数を指定するものだということをより明確に理解するため、カスタム関数を定義してみる。
import argparse
def custom_int_type(value):
# ここで任意の変換処理を行う
print(f"Received value type: {type(value)}")
return int(value)
parser = argparse.ArgumentParser()
parser.add_argument('--number', type=custom_int_type)
args = parser.parse_args()
print(f"Type of args.number: {type(args.number)}")
python main.py --number 42
# Received value type: <class 'str'>
# Type of args.number: <class 'int'>
このコードを実行すると、CLI からの入力がまず str 型として受け取られ、その後指定した関数で変換されることが確認できる。
なぜ dict や list でエラーになるのか
この仕組みを理解すると、なぜ type 引数に単純に dict 型を指定するとエラーになるのかも明らかになる。実際には内部で dict(value) のように関数が呼び出されるためであり、dict() 関数は文字列の引数を適切に処理できないからである。
また、list が CLI からの単一入力では一応動作する理由も分かる。これらの型は、単一の文字列を受け取っても適切に変換できるためだ。ただし、複数の値を受け取ることを意図している場合は nargs 引数を使用して複数の値を受け取るように指定する必要がある。
まとめ
type 引数は「型」ではなく「変換関数」を指定するものだと理解しておくと、argparse の挙動がより納得できるようになった。
Discussion