nikkie-ftnextの日記

イベントレポートや読書メモを発信

argparseで解析した引数に型をもたせる(VS Codeで解析結果の属性の型がAnyになる問題の解消方法)

はじめに

ナムコ! nikkieです。

標準ライブラリの中でとりわけヘビーユースしているargparse1
Pythonスクリプトコマンドラインツールにできます2

argparseを使ったスクリプトVS Codeで開発するうえで、型の表示を改善する小ネタです。

目次

parse_argsが返すNamespaceの属性の型がAny問題

% python -V
Python 3.11.8
import argparse
from pathlib import Path

parser = argparse.ArgumentParser()
parser.add_argument("path", type=Path)
args = parser.parse_args()

print(args.path)

parse_args()Namespaceインスタンスを返します。
Namespaceとは「単に読みやすい文字列表現を持った object のサブクラス3

args.pathの部分にマウスカーソルを当てると、型はAnyです。

parse_argsには、namespace引数がある

VS Codeで対処法はないかな〜」と、Pylanceまわりを調べていたら、こんなコメントが!
https://github.com/microsoft/pylance-release/issues/628#issuecomment-730628740

@dataclass
class Args:
    x: int
...
parser.add_argument('--x', type=int)
parser.parse_args(['--x', '123'], Args())

「こんなことできるの!?」とドキュメントを見ると、

https://docs.python.org/ja/3/library/argparse.html#the-parse-args-method

namespace - 属性を代入するオブジェクト。デフォルトでは、新しい空の Namespace オブジェクトです。

空のNamespaceにparse_args()属性を代入して返していたんですね。

取り上げたコメントの例をVS Codeで書いたとしましょう。
parse_args()は、Argsクラスのインスタンスに属性xを指定して返します。
返り値の属性xArgsクラスの属性ですから(Anyではなく)intという型が見えます。

上で使ったスクリプトを書き直しましょう。

マウスカーソルを当てると、型はPath🙌

2回型を書くのがちょっと面倒 ー Pydanticを使ってみようかな?

上の例だとデータクラスArgsと、add_argument()で2回同じ型を書くのがちょっと面倒ですよね。
浮かんだアイデアが、Pydanticのデータクラスで型変換を使い、add_argument()に型を書かずに済ませる方法。
Pydanticはパースして出力を保証してくれる4ので、argparseでは型の変換までしないという発想です。

これも動きました!5
pipx run ./script.py path/to/awesome6

終わりに

parse_argsの返り値の属性は型がAny問題、私はすっかり慣れてしまっていたのですが、「つらくないですか?」という声をきっかけに対処法を見つけることができました。
namespace引数でクラスを渡せて、クラスのインスタンスとしてパースできるんですね!
これはますますヘビーユースしてしまいそうです。


  1. 技術同人誌を書くほどです
  2. コマンドラインツールの一歩目はこちらをどうぞ
  3. https://docs.python.org/ja/3/library/argparse.html#argparse.Namespace
  4. ロバストPython』で学びました
  5. Inline script metadataを使っています。
  6. Pydanticはpipxが管理する仮想環境に入るので、VS Code上でPylanceが型を示すうえではMissingImportとなります。それでもargs.pathはPath型ですが、pipxの代わりに自分で管理する仮想環境を作ってもよいかもしれません