可変長コマンドライン引数に関する考察

今やりたいのは、次のようなことです。

$ sohtml site mysite
mysiteというサイトを作成します。
$ sohtml page sub_page
sub_pageというページを作成します。
$ sohtml html
htmlディレクトリにHTMLファイルを作成します。
$ sohtml html myhtml
myhtmlディレクトリにHTMLファイルを作成します。

つまり、site/pageコマンドに関しては、サイト名/ページ名の指定は必須ですが、htmlコマンドに関しては出力先のディレクトリはデフォルト値htmlを使用し、ディレクトリ名を指定された時だけ、そこに作成したいのです。

しかし、argparseのマニュアルを一通り読みましたが、直接これを実現する方法はありませんでした。

というわけで、argparseの機能だけでは実現できない部分に関しては、プログラムを作り込まなければなりません。

と思ってたら、add_subparsersという関数を見つけました。
この関数は、コマンドライン引数にサブコマンドを使うことができ、サブコマンド毎にコマンドライン引数を指定することができます。
こいつを使って書いたのが、次のプログラムです。

❏ sohtml.py

from argparse import ArgumentParser as ap
import html_tag

def create_site(args):
    print('{}サイトを作成します'.format(args.site_name))

def create_page(args):
    print('{}ページを作成します'.format(args.page_name))

def create_html(args):
    print('HTMLファイルを{}ディレクトリに作成します'.format(args.output_dir))

def main():
    parser = ap()
    sub_p = parser.add_subparsers()

    parser_site = sub_p.add_parser('site', help='site help')
    parser_site.add_argument('site_name')
    parser_site.set_defaults(func=create_site)

    parser_page = sub_p.add_parser('page', help='page help')
    parser_page.add_argument('page_name')
    parser_page.set_defaults(func=create_page)

    parser_html = sub_p.add_parser('html', help='html help')
    parser_html.add_argument('output_dir', nargs='?', default='htmls')
    parser_html.set_defaults(func=create_html)

    args = parser.parse_args()
    args.func(args)

if __name__ == '__main__':
    main()

(実行結果)

$ python sohtml.py page mypage
mypageページを作成します
$ python sohtml.py site mysite
mysiteサイトを作成します
$ python sohtml.py html
HTMLファイルをhtmlsディレクトリに作成します
$ python sohtml.py html myhtml_dir
HTMLファイルをmyhtml_dirディレクトリに作成します

ところが、サブコマンドを指定せずに実行すると、

$ python sohtml.py
Traceback (most recent call last):
  File "sohtml.py", line 33, in <module>
    main()
  File "sohtml.py", line 30, in main
    args.func(args)
AttributeError: 'Namespace' object has no attribute 'func'

例外が発生。

そこで、サブコマンド未入力のときはエラーになるように、以下のように修正しました。

■修正前

args.func(args)

   👇
□修正後

if hasattr(args, 'func'):
    args.func(args)
else:
    print('サブコマンドを指定して下さい')                                   

あまりパイソニックなコードではありませんが、他にいいやり方が思い浮かばないので、取り敢えずこのようにしておきます。