またもやPythonねたです。前回言っていたimportのフォーマットは、dirimportという形で公開してみました。pip install dirimport …使ってみてもらえればと。今後の自分のrequirements-dev.txtの常連になるか?
今回はclickについて。我々の日常はPythonでclickを使ってサブコマンドを作っていく毎日だが、、、そこにはパターンというものがある。
まずはサブコマンドの定型句がある。君たちはこれを何度書かせるつもりかね?
@click.group(invoke_without_command=True)
@click.pass_context
def cli(ctx):
if ctx.invoked_subcommand is None:
print(ctx.get_help())
@click.group(invoke_without_command=True)
@click.pass_context
def cli(ctx):
if ctx.invoked_subcommand is None:
print(ctx.get_help())
@click.group(invoke_without_command=True) @click.pass_context def cli(ctx): if ctx.invoked_subcommand is None: print(ctx.get_help())
で、こんな感じでサブコマンドを書いていく。
@cli.command()
@click.option("--verbose/--no-verbose")
def subcmd(verbose):
pass
@cli.command()
@click.option("--verbose/--no-verbose")
def subcmd(verbose):
pass
@cli.command() @click.option("--verbose/--no-verbose") def subcmd(verbose): pass
ここで、サブコマンド間の共通のオプションっていうものが考えられる。これは毎回いくつもの共通オプションを書くのはめんどくさいので、1つのデコレータにマージするテクニックがある。デコレータ…毎回ググらないと書ける気がしないやつね。
_common_options = [
click.option("--verbose/--no-verbose"),
click.option("--arg1"),
click.option("--arg2"),
]
def common_option(func):
for option in reversed(_common_options):
func = option(func)
return func
@cli.command()
@common_option
@click.option("--other-option")
def subcmd(verbose, arg1, arg2, other_option):
pass
_common_options = [
click.option("--verbose/--no-verbose"),
click.option("--arg1"),
click.option("--arg2"),
]
def common_option(func):
for option in reversed(_common_options):
func = option(func)
return func
@cli.command()
@common_option
@click.option("--other-option")
def subcmd(verbose, arg1, arg2, other_option):
pass
_common_options = [ click.option("--verbose/--no-verbose"), click.option("--arg1"), click.option("--arg2"), ] def common_option(func): for option in reversed(_common_options): func = option(func) return func @cli.command() @common_option @click.option("--other-option") def subcmd(verbose, arg1, arg2, other_option): pass
しかし全部のサブコマンドに共通ってわけじゃなくて、複数の共通セットがあってサブコマンドの種類ごとに変わります、みたいなことになると、、、これで個別にデコレータを増やしていくのもきつくなる。どうするか…
そこで、引数のオプション配列を処理してくれるデコレータにする、というテクニックが出てくる。
def multi_option(decs):
def deco(f):
for dec in reversed(decs):
f = dec(f)
return f
return deco
@cli.command()
@multi_option(_common_options)
@click.option("--other-option")
def subcmd(**kwargs):
pass
def multi_option(decs):
def deco(f):
for dec in reversed(decs):
f = dec(f)
return f
return deco
@cli.command()
@multi_option(_common_options)
@click.option("--other-option")
def subcmd(**kwargs):
pass
def multi_option(decs): def deco(f): for dec in reversed(decs): f = dec(f) return f return deco @cli.command() @multi_option(_common_options) @click.option("--other-option") def subcmd(**kwargs): pass
実際はmulti_optionsからcommon_optionを作ることになるんだろう。
def common_option1(func):
return multi_option(_common_options)(func)
@cli.command()
@common_option1
@click.option("--other-option")
def subcmd(**kwargs):
pass
def common_option1(func):
return multi_option(_common_options)(func)
@cli.command()
@common_option1
@click.option("--other-option")
def subcmd(**kwargs):
pass
def common_option1(func): return multi_option(_common_options)(func) @cli.command() @common_option1 @click.option("--other-option") def subcmd(**kwargs): pass
ここまでやるとclickにこだわるんじゃなくて、argparseでいいような気もするね。
def common_parser(parser=None):
if parser is None:
parser = argparse.ArgumentParser()
parser.add_argument(...)
parser.add_argument(...)
return parser
def main():
p = common_parser()
p.add_argument("--other-option")
opt = p.parse_args()
:
def common_parser(parser=None):
if parser is None:
parser = argparse.ArgumentParser()
parser.add_argument(...)
parser.add_argument(...)
return parser
def main():
p = common_parser()
p.add_argument("--other-option")
opt = p.parse_args()
:
def common_parser(parser=None): if parser is None: parser = argparse.ArgumentParser() parser.add_argument(...) parser.add_argument(...) return parser def main(): p = common_parser() p.add_argument("--other-option") opt = p.parse_args() :
もうちょっと頑張って何か作ったら、うまいこと省力化できそうな気もしますよね。どうしようかな。