nikkie-ftnextの日記

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

argparseはオプション引数だけでなく、位置引数も扱えます!!(#Python実践レシピ を勝手に補足)

はじめに

シン・エヴァ、最優秀賞おめでとうございます!1 nikkieです。

この1月に出た書籍『Pythonエンジニア育成推進協会監修 Python実践レシピ』(以下、『Python実践レシピ』)を読んでいます。
Python標準ライブラリのargparseについて、書籍での説明を補足したいと強く思い、この記事を書きました。

Python実践レシピ』は、豊富なライブラリを紹介しています。
リファレンス的に読んでいますが、argparseの取り上げ方については、「情報量が少なく、かえって誤解されてしまうかも」と感じました。
読んだ方が"実践"しやすくなるよう、この記事で勝手に補足します。

おことわり

  • nikkieは『Python実践レシピ』の執筆にもレビューにも関わっておらず、この記事は『Python実践レシピ』的には 非公式 情報です(非公式だから、「勝手に 補足」と言っています)
  • nikkieが勝手にやっていることではありますが、情報の 正確性は追求 したく、公式ドキュメントなど信頼できると考える情報源をソースにします

用語の定義(公式ドキュメントの翻訳にならっています)

  • 位置引数:positional arguments
  • オプション引数:optional arguments

目次

この記事で強く伝えたいこと

Python実践レシピ』を読んでargparseを初めて知った方へ

Python実践レシピでのargparseの紹介

「Chapter10 汎用OS・ランタイムサービス」の10.4でargparseが紹介されています。

10.4 コマンドラインオプション,引数を扱う―argparse

例として登場するのは、文字列を繰り返すPythonスクリプトです(10.4.1 コマンドラインオプションを扱う)。
スクリプトへの引数(オプション)として、(1)文字列、(2)繰り返し回数の2つを入力します。

呼び出し方は以下のようになります。

$ # python repeat.py -s 文字列 -n 繰り返し回数
$ python repeat.py -s シオン -n 3
シオンシオンシオン

オプションを指定する順番に意味はないので、python repeat.py -n 3 -s シオン と指定しても同じ結果が得られます。
繰り返し回数(-nオプション)として3を、文字列(-sオプション)としてシオンを指定したことには変わりありませんからね。

繰り返し回数の引数は省略できるように実装されています。
省略した場合は2が指定されたものとします。

$ python repeat.py -s シオン
シオンシオン

Python実践レシピ』は非常に意欲的に、豊富なライブラリを紹介しており、argparseの例をClick2で書き換える例も紹介しています。

「紙幅の都合もあり、Python実践レシピでは"オプション"としての用途に振り切って執筆したのかな」と捉えていまして、以下でargparseについて補足していきます。

📣位置引数にできる!

※詳しく知りたい方は、チュートリアルの「位置引数の入門」をご覧ください:
https://docs.python.org/ja/3/howto/argparse.html#introducing-positional-arguments

文字列を1つ目の引数、繰り返し回数を2つ目の引数に取るように書き換えられます。

呼び出し方

$ # python repeat.py 文字列 繰り返し回数
$ python repeat.py シオン 3
シオンシオンシオン

引数を指定する順番に意味をもたせているということです。

書き換える前と同様に、繰り返し回数(2番めの引数)を省略してスクリプトを呼び出すこともできます。

$ python repeat.py シオン
シオンシオン

実装

help引数で指定するヘルプメッセージは省略しています。

import argparse

parser = argparse.ArgumentParser(description="Example command")
parser.add_argument("string")
parser.add_argument("num", type=int, nargs="?", default=2)
args = parser.parse_args()

print(args.string * args.num)

書籍のコードから以下のように変更しました。

  • オプション引数ではなく、位置引数とする(add_argumentの第1引数に渡す文字列を変更)
  • 繰り返し回数は、省略できる位置引数とする(add_argumentnargs引数を利用)

オプション引数から位置引数への書き換えポイント

引数名を-から始めないことで、位置引数とする

add_argumentの第1引数は-から始めないことで、位置引数として指定しました。

parse_args() が呼ばれたとき、オプション引数は接頭辞 - により識別され、それ以外の引数は位置引数として扱われます:

https://docs.python.org/ja/3/library/argparse.html#name-or-flags

位置引数はオプション(指定が任意)ではなく、指定が必須になっているため、第1引数stringは、required引数の指定が不要となります。
残っていると、「TypeError: 'required' is an invalid argument for positionals」が送出されます(「required引数は位置引数には無効」とのこと)。

parser.add_argument("string")

第2引数numは、省略できる位置引数

位置引数としての指定を省略した場合にデフォルト値を使うよう、nargs引数とdefault引数を指定しています。

parser.add_argument("num", type=int, nargs="?", default=2)

オプション引数にrequired=Trueを指定するのは「一般的には悪いやり方」

Python実践レシピ』のコードは、文字列のオプション(-s)の指定を必須にするためにrequired引数にTrueを渡しています。

parser.add_argument("-s", "--string", required=True)

この点について、argparseのドキュメントに「一般的には悪いやり方」という記載を見つけました。

注釈: ユーザーは、通常 フラグ の指定は 任意 であると認識しているため、必須にするのは一般的には悪いやり方で、できる限り避けるべきです。

https://docs.python.org/ja/3/library/argparse.html#required

Python実践レシピ』の記載だけを読むと、「required=Trueと指定すればいいのか」と誤解してしまうかもしれません。

書籍は情報が正確である(例えば、公式ドキュメントに沿っている)ことを私は重視しています。
初めて知る読者が間違った使い方を実践してしまわないよう、『Python実践レシピ』のコードはrequired=Trueと書かなくて済むように、位置引数で紹介したほうが適切だったのではないかと考えます。

Zen of Pythonには「Special cases aren't special enough to break the rules. Although practicality beats purity.3」とありますね。
オプション引数でrequired=Trueを使うというのは特別扱いだと思います。
どうしても特別扱いしなくてはいけない理由が私には見出だせていないので、ルールを破らず位置引数で実装したいかなという意見です。

type=strは省略できると考えます

https://docs.python.org/ja/3/library/argparse.html#type

デフォルトでは、ArgumentParser オブジェクトはコマンドライン引数を単なる文字列として読み込みます。

第1引数はデフォルトの挙動で問題ないので、type=strという指定はしませんでした。

私はintfloatに変えたいとき(文字列として扱われると困るとき)だけ、type引数を指定しています。
パスを表す文字列をpathlib.Pathに変えることもありますね。

argparseのチュートリアルやドキュメントのコードで、add_argumenttype=strが指定されている例はほとんど見ないので、省略してしまっていいのではないかと考えています。

IMO:『Python実践レシピ』のargparseのコードをnikkieが書くなら

ここで実装を示したように、位置引数を使います。 オプション引数は使いません。

理由は2点あり、どちらも私の好みによるものです。

  • -s,-nというオプションを毎回入力するのが面倒だから(インターフェースに関する好み)
    • 最初にヘルプメッセージを表示して、位置引数の順番を確認したほうが手っ取り早いと感じます
  • オプション引数の指定を必須にするためにrequired=Trueとするのは、ドキュメントによれば一般的に悪いやり方だから(一般的に悪いやり方は採用したくないという好み)

Python実践レシピ』には引数が1つのケースをオプション引数で実装する例も登場します(10.4.4)が、その場合も私だったら位置引数とします。

終わりに

Python実践レシピ』で紹介されたargparseについて、オプション引数だけでなく、位置引数もあることを補足しました。

書籍は紙面が限られており、その中で豊富なライブラリを紹介するという『Python実践レシピ』のスタンスは素晴らしいと思います。
そして、この本は認定試験の教科書でもあるため、多くの方が読むと期待されます。
残念ながらargparseについては説明が十分とは感じられず、「情報量が少ないためにargparseへの誤解が広がってはいけない」と勝手に補足しました。

Python公式ドキュメントを翻訳されているcocoatomoさんが、以下のように書いています。

Python公式ドキュメントが取り零している部分は、きっと他のブログや勉強会の発表で解説されるでしょう。

Python実践レシピ』についても、この記事が補完関係になったらいいなと思っていますし、今後も勝手に補足していきたいと考えています。

(執筆者の方々やレビュアーの方々には顔見知りの方もいらっしゃるので、)私の補足の仕方として至らない点があれば、TwitterやSlackのDMでフィードバックいただければ大変ありがたいです。


  1. 優秀賞の『アイの歌声を聴かせて』も面白いからみんな観て!(このブログのお約束ですね)

  2. 「Command Line Interface Creation Kit」、略してClickだそうで、コマンドを作るのをサポートする、サードパーティライブラリです(argparseと同じことができると理解しています) https://click.palletsprojects.com/en/8.0.x/

  3. 「特別だと思うものもルールを破るほど特別ではない。ただし、実用に耐えるようにする」(『パーフェクト Python [改訂2版]』より)