【Python】クラスで定義するコマンドラインパーサー
はじめに
本記事では、筆者が作成した『クラスでコマンドラインパーサーを定義できるライブラリclassopt 』を紹介します。
このライブラリはRustのStructOptを参考に作成した、コマンドライン引数を解析するためのライブラリであり、Pythonの標準ライブラリであるargparseの変数の型が推定できないという問題を解決しています。
変数の型を推定できるようにすることで、VSCodeなどのエディタでコーディングする際に、引数に対する補完(インテリセンス)を効かせることができます。
argparseを使った際のコーディング
classoptを使った際のコーディング
argparseを使った場合、解析するコマンドライン引数は動的に定義されるため、静的解析で引数名と型が解析できておらず、インテリセンスが効いていません。
一方で、classoptを使った場合、解析するコマンドライン引数はクラスで定義されており、静的解析で引数名と型がわかるため、インテリセンスを効かせることができます。
型が解析可能であるため、例えばstr
型のコマンドライン引数の場合だと、str
のメソッドであるsplit
など、それぞれの型で定義されたメソッドも補完で出すことが可能です。
このように補完が効く部分を増やすことで、長い変数名を簡単に入力できるだけでなく、タイポなどのミスも減らすことができ、快適なコーディングを行なうことができるようになります。
Install & Usage
Install
PyPIで公開しており、pipでインストールすることができます。
pip install classopt
Usage
classoptでは、デコレータを活用してコマンドラインパーサーの定義を行います。
まずはシンプルな例を用いて説明を行います。
from classopt import classopt
@classopt
class Args:
count: int
text: str
args: Args = Args.from_args()
print(args.count)
print(args.text)
実行例
$ python demo.py 10 hello
10
hello
コマンドラインパーサーを作成するために、デコレータclassopt
でデコレートしたクラスを定義します。
そして、このクラス内で<引数名>: <型>
の形で解析するコマンドライン引数を定義します。
これは内部で用いているdataclasses.dataclassと同じ定義方法です。
また、classoptは内部でargparseを用いており、config
関数を用いることで、ArgumentParser.add_argumentで指定することができる全てのオプションを指定することができます(オプション引数と位置引数の指定以外)。
オプション引数と位置引数の指定は、ArgumentParser.add_argument
では、以下のように引数名の指定と同時に行います。
# 位置引数
parser.add_argument("foo")
# オプション引数
parser.add_argument("-b", "--bar")
一方、classoptの場合は引数名はクラス定義時に指定しているので、config
関数でオプション引数か位置引数かを指定します。
具体的には、config
関数にshort=True
を渡すとショートオプション、long=True
を渡すとロングオプションを引数名から自動で定義し、どちらも渡さなければ位置引数となります。
@classopt
class Args:
# 位置引数
foo: str
# オプション引数
bar: str = config(long=True, short=True)
# bar: str = config(long="--foo", short="-f") のように別名のオプション引数も指定可能
他にも便利な機能として、@classopt(default_long=True, default_short=True)
のようにデコレータにdefault_long=True
やdefault_short=True
を渡すことで、全てのコマンドライン引数をオプション引数化することができます。
また、型としてlist[型]
を与えるとnargs="*"
を自動で定義し、bool
を与えるとaction="store_true"
を自動で定義してくれます。
まとめると、classoptは以下のように使うことができます。
from classopt import classopt, config
@classopt(default_long=True, default_short=True)
class Args:
paths: list[str] = config(help="Input paths")
flag: bool = config(help="Input flag")
args: Args = Args.from_args()
print(args.paths)
print(args.flag)
実行例
$ python demo.py --paths sample/path1 sample/path2 -f
['sample/path1', 'sample/path2']
True
$ python demo.py --help
usage: demo.py [-h] [--paths [PATHS ...]] [--flag]
optional arguments:
-h, --help show this help message and exit
--paths [PATHS ...], -p [PATHS ...]
Input paths
--flag, -f Input flag
おわりに
本記事では、クラスで定義するコマンドラインパーサーclassoptを紹介しました。
Pythonでのコーディングを快適にできる便利なライブラリなので、是非使ってみてください。
Discussion
既にご存知かもしれませんが、似たようなものでclickやtyperなどがあります。
この辺との差別化がアピールできるとさらに良くなると思います!
例えば、clickやtyperは引数をクラスで指定しないけど、classoptならできるから○○という点で良い!みたいな。
応援してます!
コメントありがとうございます!これらのライブラリは知らなかったので、参考になります🙇♂️
ぱっと思いついた差別化だと、コマンドライン引数の定義が関数と結合しておらず、独立しているとかでしょうか(見やすい、別ファイルに切り出せる)。
また時間があるときに、記事やREADMEに追記しておこうと思います!
似たような時期に似たようなコンセプトのライブラリを作っていてビックリしましたw
僕は開発止めちゃったので、いちstructopt好きとして応援しています 👍
コメントありがとうございます!記事とリポジトリを拝見しましたが、かなり似ていて自分も驚きました😲
良いライブラリにできるように、これからも頑張ります!