コマンドライン引数の扱い方にはいくつかの方法がありますのでメモしておきます。 もちろん sys.argv を自前で解析すれば何とでもなりますが、その解析を頑張りたくはありませんね、という動機です。
- 標準ライブラリ
- フレームワークに特有の方法 - Django, Twisted, Tornado
- 独立したライブラリ - gflags
標準ライブラリ
Python の標準ライブラリには次の3種類があります。
- getopt — C-style parser for command line options
- optparse — Parser for command line options
- argparse — Parser for command line options, arguments and sub-commands
Python 以外のプログラミング言語でも同じような感じで使える、という意味で getopt は分かりやすいと言えます。 しかし、Python だけに絞れば optparse が便利です。 Python 2.7 からは optparse ではなく argparse を推奨されます。 対象とするスクリプトをどこで動かすか?という点がひとつの判断基準になると思います。
いずれも公式ドキュメントのコード例が充実していますので、見れば分かる、という感じが嬉しいところです。 チュートリアルとしては Dive Into Python を読む、ということで。
- Handling command-line arguments (Dive Into Python)
たとえば、 -v と -q でログのオプションを変更する関数は次にように記述できます。
__doc__ = '''simple command line arguments and options parser.''' import logging import optparse def parse_args(): parser = optparse.OptionParser(__doc__) parser.add_option("-v", "--verbose", dest="verbose", default=False, action="store_true", help="verbose mode") parser.add_option("-q", "--quiet", dest="verbose", default=True, action="store_false", help="quiet mode") opts, args = parser.parse_args() if not args: parser.error("no parsing file is specified.") if opts.verbose: logging.basicConfig(level=logging.DEBUG) return opts, args
フレームワークに特有の方法
有名っぽいものを3つ。 いずれも easy_install や pip でインストールできます。
- Django
- Twisted
- Tornado
Django
Django は、Python ではたぶんもっとも広く使われている Web フレームワークです。
アプリケーションの management/commands ディレクトリに django.core.management.base.BaseCommand を継承したクラスを実装します。 option_list を定義しておくことで、 handle メソッドにキーワード引数が渡ってきます。 公式ドキュメントが充実していますので、リンク先のページが最も詳しいでしょう。
コマンドを定義しておくと、 manage.py に引数として与えることで自動的に探し出してくれます。 注意としては、各ディレクトリに __init.py__ を配置しておかないと検出できない、という点でしょうか。 とはいえ、本当に簡単なスクリプトのために Django を使うことはないと思いますので、 Python がモジュールを import するお作法どおり、ということで。
Twisted
Twisted はネットワーク処理用のライブラリです。
- Parsing command-lines with usage.Options (Twisted Documentation)
Twisted の場合はいくつかの方法があります。 まずは普通の処理。 Django の option_list と似た感じで optFlags や optParameters を定義します。
from twisted.python import usage class Options(usage.Options): optFlags = [["verbose", "v", "verbose mode"],] optParameters = [["port", "p", 8080, "run on the given port", int],] def main(): options = Options() try: options.parseOptions() except usage.UsageError, errortext: raise SystemExit('%s, use --help' % (errortext,)) if options['verbose']: print "Verbose mode." print "Port #", options['port'] if __name__ == '__main__': main()
option-twisted.py として保存して実行すると、次のようになります。
$ python option-twisted.py --help Usage: option-twisted.py [options] Options: -v, --verbose verbose mode -p, --port= run on the given port [default: 8080] --version --help Display this help and exit. $ python option-twisted.py -v Verbose mode. Port # 8080 $ python option-twisted.py --port=8888 Port # 8888
Options ではサブコマンドも扱えます。
from twisted.python import usage class ImportOptions(usage.Options): optParameters = [['module', 'm', None, None], ['release', 'r', None]] class CheckoutOptions(usage.Options): optParameters = [['module', 'm', None, None], ['tag', 'r', None, None]] class Options(usage.Options): subCommands = [['import', None, ImportOptions, "Do an Import"], ['checkout', None, CheckoutOptions, "Do a Checkout"]] PROC = { 'import': lambda(opt): ("import", opt), 'checkout': lambda(opt): ("checkout", opt) } def main(): options = Options() try: options.parseOptions() except usage.UsageError, errortext: raise SystemExit('%s, use --help' % (errortext,)) if not options.subCommand: raise SystemExit('unknown command, see --help') print PROC[options.subCommand](options.subOptions) if __name__ == '__main__': main()
option-twisted-subcmd.py として保存して実行すると、次のようになります。 スクリプト自体に --help オプションを渡した場合にはコマンドの一覧が表示され、 コマンドに --help オプションを渡した場合には、そのコマンドのヘルプが表示されます。
$ python option-twisted-subcmd.py --help Usage: option-twisted-subcmd.py [options] Options: --version --help Display this help and exit. Commands: import Do an Import checkout Do a Checkout $ python option-twisted-subcmd.py import --help Usage: option-twisted-subcmd.py [options] import [options] Options: -m, --module= -r, --release= --version --help Display this help and exit. $ python option-twisted-subcmd.py import -m abc -r 1 ('import', {'release': '1', 'module': 'abc'}) $ python option-twisted-subcmd.py checkout --help Usage: option-twisted-subcmd.py [options] checkout [options] Options: -m, --module= -r, --tag= --version --help Display this help and exit. $ python option-twisted-subcmd.py checkout -m def -r 2 ('checkout', {'tag': '2', 'module': 'def'})
Twisted には twistd と呼ばれるスクリプトも付属しています。 これはデーモン化するための諸々を扱ってくれるもので、 説明は Twisted Daemonologie (日本語) が詳しいと思います。 低レベルな部分からの話なので非常に長いですが...
twistd にコマンドを追加するためには、Django のようにフレームワークで決められたディレクトリに、 IPlugin クラスを実装したものを定義します。 twistd の場合は「プラグイン」という単位で、このためのディレクトリは twisted/plugins になります。 基本的にはカレントディレクトリから考えるのが間違いが少ないと思いますが、 twistd の -d オプションで切り替えられます。この辺は好きずき、ということで。 プラグインの名前は tapname で指定します。 既存のプラグインと重複しない名前を選ぶ必要があります。
from zope.interface import implements from twisted.python import usage, log from twisted.plugin import IPlugin from twisted.application import service class Options(usage.Options): optParameters = [["port", "p", 8080, "run on the given port", int],] class SimpleServiceMaker(object): implements(service.IServiceMaker, IPlugin) tapname = "simpleservice" description = "A simple service." options = Options def makeService(self, options): top_service = service.MultiService() log.msg("make service with some options: " + repr(options)) return top_service service_maker = SimpleServiceMaker()
これを twisted/plugins/option_twistd.py として保存します。 途中のディレクトリに __init_.py を置く必要はありません。 ファイルとプラグイン名は別々で構いませんが、途中にハイフンなどは使えないようです。 とはいえ、プラグイン名とファイル名を揃えておいた方が管理しやすいでしょう。(ここでは例示のために別々にしている)
$ twistd --help |grep simpleservice simpleservice A simple service. $ twistd simpleservice --help Usage: twistd [options] simpleservice [options] Options: -p, --port= run on the given port [default: 8080] --version --help Display this help and exit. module(name[, doc]) Create a module object. The name must be a string; the optional doc argument can have any type. $ twistd --nodaemon simpleservice --port=8888 (snip) reactor が開始されるログが表示されます。 Ctrl+C で停止できます。
改めて Options を確認すると、 twistd だから特別、ということはありません。 どの場面でも同じオプション用のデータとして扱えます。
この他にも Twisted の場合は、後処理や同一オプションのカウントなども簡単に実現可能です。 詳しくは公式ドキュメントに記載されています。 twistd についても、 .tac ファイルを使う、という選択肢もあります。 こちらも詳しくは公式ドキュメントに記載されています。
Tornado
Tornado はノンブロッキングの Web サーバフレームワークです。
- Tornado Web Server Documentation
- Tornadoウェブフレームワーク日本語訳ができるまで (渋日記)
- Pythonフレームワーク「Tornado」を使用し、JSON形式のデータを公開する (Symfoware)
たとえば、Web サーバのポート番号をオプションで取得するコードです。
import tornado.options tornado.options.define( "port", default=8080, type=int, help="run on the given port") def main(): try: tornado.options.parse_command_line() except tornado.options.Error, e: raise SystemExit(e) port = tornado.options.options.port print "Port #", port if __name__ == '__main__': main()
これを option-tornado.py として保存して実行すると次のようになります。
$ python option-tornado.py --help Usage: option-tornado.py [OPTIONS] Options: --help show this help information --log_file_max_size max size of log files before rollover --log_file_num_backups number of log files to keep --log_file_prefix=PATH Path prefix for log files. Note that if you are running multiple tornado processes, log_file_prefix must be different for each of them (e.g. include the port number) --log_to_stderr Send log output to stderr (colorized if possible). By default use stderr if --log_file_prefix is not set and no other logging is configured. --logging=info|warning|error|none Set the Python log level. If 'none', tornado won't touch the logging configuration. option-tornado.py --port run on the given port $ python option-tornado.py Port # 8080 $ python option-tornado.py --port=8888 Port # 8888
ログに関する設定がデフォルトで組み込まれており、独自に定義したオプションはその下に表示されます。
独立したライブラリ - gflags
python-gflags は Google がオープンソースとして公開しているライブラリで、 C++ 用の google-gflags の Python 版です。 基本的な機能は標準ライブラリのものと同様ですが、「そのオプションが使われるソースファイルでフラグを定義できる」という点がユニークです。
gflags モジュールは google-api-python-client でも利用されています。 たとえば次のように使用できます。 gflags.py 自体に使い方が詳述されていますので、英語を読解すれば何とかなりそうです。
import logging import os import sys import gflags from apiclient.discovery import build from oauth2client.file import Storage from oauth2client.client import OAuth2WebServerFlow from oauth2client.tools import run FLAGS = gflags.FLAGS gflags.DEFINE_enum('logging_level', 'ERROR', ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], 'Set the level of logging detail.') gflags.DEFINE_string('basedir', os.path.join(os.environ['HOME'], '.google_api'), 'Directory where credentials saved.') gflags.DEFINE_integer('day_term', 7, 'Term of days to begin with.', lower_bound=0) def main(argv): try: argv = FLAGS(argv) except gflags.FlagsError, e: print '%s\nUsage: %s ARGS\n%s' % (e, argv[0], FLAGS) sys.exit(1) logging.getLogger().setLevel(getattr(logging, FLAGS.logging_level)) print "Base directory:", FLAGS.basedir print "Term:", FLAGS.day_term if __name__ == '__main__': main(sys.argv)
これを option-gflags.py として保存して実行すると次のようになります。
$ python option-gflags.py --help USAGE: option-gflags.py [flags] flags: option-gflags.py: --basedir: Directory where credentials saved. (default: '/Users/shigeru/.google_api') --day_term: Term of days to begin with. (default: '7') (a non-negative integer) -?,--[no]help: show this help --[no]helpshort: show usage only for this module --[no]helpxml: like --help, but generates XML output --logging_level: <DEBUG|INFO|WARNING|ERROR|CRITICAL>: Set the level of logging detail. (default: 'ERROR') apiclient.model: --[no]dump_request_response: Dump all http server requests and responses. Must use apiclient.model.LoggingJsonModel as the model. (default: 'false') oauth2client.tools: --auth_host_name: Host name to use when running a local web server to handle redirects during OAuth authorization. (default: 'localhost') --auth_host_port: Port to use when running a local web server to handle redirects during OAuth authorization.; repeat this option to specify a list of values (default: '[8080, 8090]') (an integer) --[no]auth_local_webserver: Run a local web server to handle redirects during OAuth authorization. (default: 'true') gflags: --flagfile: Insert flag definitions from the given file into the command line. (default: '') --undefok: comma-separated list of flag names that it is okay to specify on the command line even if the program does not define a flag with that name. IMPORTANT: flags in this list that have arguments MUST use the --flag=value format. (default: '') $ python option-gflags.py Base directory: /Users/shigeru/.google_api Term: 7 $ python option-gflags.py --basedir=/etc/google_api Base directory: /etc/google_api Term: 7
import 先のオプション (oauth2client.tools)、およびその先の import (apiclient.model) のオプションも扱うことができます。 oauth2client.tools は OAuth 2.0 の認証フローを実行するためのものです。 ライブラリで隠蔽してある設定情報も簡単に与えられるのは便利だと思います。 ここではコマンドライン引数 (sys.argv) を与えていますが、これは単なるリストですから、 任意の実行環境 (たとえば Google App Engine) でオプションを制御できますね。
終わりに
コマンドラインオプションの扱い方をざーっと見てみました。 フレームワークを使う場合には、そのお作法に沿って記述するのがマナーだと思いますが、 それぞれの方法ごとにヘルプメッセージや入力値のチェックが異なりますので、 たまには比較してみるのも良いのかな、と感じます。
- 組込みのオプション項目
- 出力されるヘルプテキスト
- ショートオプションとロングオプションを対応付ける方法
- デフォルト値の設定方法
- データ型の限定、制限値の定義方法
- 選択肢を限定する方法
- オプション定義を記述できる場所
とはいえ、 gflags の使い方をメモしておきたかった、というのが一番です。 ライブラリコードの中でオプションを定義できることは一長一短あるでしょうが、 OAuth 2.0 のリダイレクトコールバックを受け取るような用途だと便利だな、と思いました。
0 件のコメント:
コメントを投稿