Pythonにはコマンドライン引数をパースする標準モジュールがgetopt・optparse・argparseの3種類あります。
このうちoptparseは14年も前の2011/02/20にリリースされた3.2から非推奨になっています。
そしてgetoptは先日2024/10/07にリリースされた3.13で非推奨になりました。
従って、今後はargparseに一本化されます。
となるはずだったのですが、一本化されるどころか逆に3.14でoptparse・getoptの非推奨が外されました。
すなわち、argparse・optparse・getoptいずれも同列で使用してかまわない機能であるということになりました。
optparseなんか14年経ってから復活です。
どうしてこんなことが起きたのでしょう?
ソフトな非推奨
Pythonの非推奨には通常のDeprecationとSoft Deprecationの2種類があります。
通常のDeprecationは破壊的変更であり、今後のリリースにおいて削除される可能性があります。
Soft Deprecationは開発の打ち切りであり、今後の機能追加や改善はされないため、使用することはあまり推奨されません。
ただし、いずれ通常のDeprecationプロセスに移行する可能性は高いですが、通常のDeprecationプロセスを経ずにいきなり削除されることはありません。
optparse・getoptはいずれもソフトな非推奨でした。
各パーサの説明
getopt
Python1.0の初期リリースより前から存在。
Python3.13で非推奨。
getoptはC言語由来のパーサです。
POSIXで規定されており、Linuxをはじめ数多くの言語にそのまま実装されており、互換性が極めて高く、事実上の言語間標準となっています。
もちろんPHPにもあるよ。
機能が少ないぶん非常に安定しており、バグが最後に見つかったのは2008年のことです。
optlist, args = getopt.getopt(text, 'q', ['file='])
これでscript.py --file=outfile -q
を受け取れます。
optparse
Python2.3で実装。
Python3.2で非推奨。
optparseはオブジェクトインターフェイスを搭載したパーサーです。
import optparse
parser = optparse.OptionParser()
parser.add_option("-f", "--file", dest="filename",
help="write report to FILE", metavar="FILE")
parser.add_option("-q", "--quiet",
action="store_false", dest="verbose", default=True,
help="don't print status messages to stdout")
(options, args) = parser.parse_args()
これでscript.py --file=outfile -q
を受け取れます。
argparse
Python2.7 | 3.2で実装。
argparseはoptparseをさらに発展させたパーサーです。
optparseは設計上の問題で拡張が難しいらしく、互換性のないパーサーとして別途作成されました。
どうも当初は互換を保とうとしていたけどできなかったので別にしたみたいなことが書いてありました。
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-f', '--file', help="write report to FILE", metavar='FILE')
parser.add_argument('-q', '--quiet')
args = parser.parse_args()
これでscript.py --file=outfile -q
を受け取れます。
Getopt and optparse vs argparse
Pythonのコア開発者のひとりSerhiy Storchakaによる投稿です。
しばらくargparseの改修に取り組んでいます。
当初は185件のIssueがあり、その半分はバグレポートであり、そしてIssueを半分に減らしました。
しかし残りのバグは、仕様を変更せずに解決することは困難です。
argparseには多くの機能がありますが、それらが常に協調して動作するわけではありません。
またargparseの挙動はPOSIXやGNUのほとんどのパーサと異なっており、その一部はバグとみなされるほどです。
そして、この問題の原因はargparseの設計の根幹に存在します。
ちょっとしたインターフェイスを構築するだけでも、エッジケースにおいて予期しない動作が発生することがあります。
さらに複雑なインターフェイスを構築したい場合、どうすればよいかの質問に対する答えの多くはこうなります。
『argparseでは不可能なので、getoptかoptparseを使ってください。』
getoptとoptparseは、堅牢で成熟しており、バグもほとんど存在しないにもかかわらず、現在は非推奨になっています。
getoptとoptparseの非推奨を解除することを提案します。
getoptは決して非推奨にするべきではありません。
getoptはシンプルで、POSIX標準であり、他言語から来たプログラマーもgetoptにはなじみがあります。
optparseには見慣れないオブジェクト指向APIがありますが、十分に予測可能な挙動であり、argparseの問題が解決するまではoptparseを推奨するべきです。
このひと、optparseを代替する唯一のCLI解析モジュールとしてargparseを推奨したのは失敗だった、argparse利用者のほとんどは宣伝の被害者だとまで言っています。
もっともargparseを使うなと言ってるわけではなく、従来のCLIを実現するシンプルなAPIと、より機能が豊富だが互換のないCLI、いずれかをユーザの手で選択するように伝えるべきだった、という主張です。
その他のコメント
IDLEはgetoptを使用しています。
optparseが非推奨だったからargparseの使用を検討したけど、Issueを見てがっかりしたから使っていません。
clickはoptparseを使用しています。
わざわざWhy not Argparse?という項目を用意して、argparseの問題点を指摘しています。
pipはoptparseを使用しています。
argparseの問題点
オプション-f
に引数が必須である場合、-f -b
と書くと通常のCLIでは-f
がオプションで-b
はその引数となる。
しかしargparseでは-f
に引数が足りないというエラーになる。
argparseではこの場合、空白を置かずに-f-b
と書かないといけない。
しかし引数が=
で始まる場合は-f=-b
では動かず、-f==-b
と書かないといけない。
こんな書き方は他のCLIではサポートされておらず、互換性がない。
またこの回避策は、引数が複数ある場合は全くうまく動かない。
それ以外にも、仕様的に解決できない問題点がいくつか指摘されています。
スレッドの続き
だいたいエッジケースだから非推奨じゃなくしたり非推奨にしたりせず裏側でやればいいんじゃないかな。
いや引数にマイナス値を与えようとしてめっちゃバグに遭遇したんだが。
Clickの開発者 Clickはoptparseを使っていないけど、それはClickのやりたいことがoptparseと異なっているからで、決してoptparseが役に立たないとかではないよ。
最終的にこのスレッドは、何人かの中身が分かっているコア開発者と、自転車置き場の屋根の色理論に群がってきた素人によってぐだぐだになりました。
感想
argparseは仕様上の欠陥があるから、複雑なことをしたいならgetoptかoptparseを使えということでした。
argparseに移行してから10年以上も経っているのに今さらそんなこと言ってくるんか。
実際は既にargparseの利用が圧倒的に多く、非推奨が外れたとしても既存のプロジェクトをわざわざoptparseに書き直したりすることはないでしょうが、今後の新規プロジェクトでgetoptの採用が多少増えたりすることがあるかもしれませんね。
getopt:108k
optparse:127k
argparse:3.7M
それにしてもPython、AIでの採用などにより言語自体の利用は増えているのに、肝心の開発側がどうも色々gdgdなんですよね。
最近記事にしただけでもTimSortとThe Zen of Pythonの開発者を3ヶ月BANしたりPython作者の発言を検閲したりしていますが、ログを調べてみるとそれ以外にも色々楽しい過去が出てくること出てくること。
そりゃもうGuidoがBDFL辞めたくなるのも納得というものでしたよ。
なお最近のGuide van Rossumは、追放中のTim Petersから教えを受けて、Python理事会の投票方式を新しくすることを検討しています。
はて、なにか思うところがあったのでしょうかね?