CLI是“命令行界面”
@click.command() 裝飾一個函數(shù),就能使之成為命令行
@click.option() 添加命令行選項
@click.group 把函數(shù)A裝飾為Group對象,就可以擁有多個子命令
后面用 A.add_command(hello) 就把hello函數(shù)加入到A中了
注意, hello 是被裝飾成命令行的函數(shù)
也可用 @A.group() 裝飾函數(shù) hello ,效果是一樣的
Basic Concepts #
裝飾器是一個函數(shù),它接受另一個函數(shù)作為參數(shù),并返回一個新函數(shù)。
Click是基于裝飾器的。裝飾器將函數(shù)變成一個可調(diào)用的腳本(命令):
A function becomes a Click command line tool by decorating it through click.command(). At its simplest, just decorating a function with this decorator will make it into a callable script:
import click
@click.command()
def hello():
click.echo('Hello World!')
What’s happening is that the decorator converts the function into a Command
which then can be invoked:
if __name__ == '__main__':
hello()
And what it looks like:
$ python hello.py
Hello World!
And the corresponding help page:
$ python hello.py --help
Usage: hello.py [OPTIONS]
Options:
--help Show this message and exit.
Options
Callbacks and Eager Options #
有時想用一個參數(shù)就改變執(zhí)行流程。比如輸入 --version 參數(shù),就不執(zhí)行腳本,而只是在屏幕上輸出版本信息。
這就出現(xiàn)了兩個概念:eager參數(shù)和callback,eager就是優(yōu)先級(--version的要求比其他參數(shù)更急切),回調(diào)函數(shù)就是要執(zhí)行的動作(輸出版本信息)。
callback 有兩個參數(shù):the current Context and the value. 前者(是Click提供的一個內(nèi)部對象)提供一些功能,比如退出應(yīng)用程序和 gives access to other already processed parameters.
def print_version(ctx, param, value): # 這里的ctx就是Context
if not value or ctx.resilient_parsing:
return
click.echo('Version 1.0')
ctx.exit()
@click.command()
@click.option('--version', is_flag=True, callback=print_version,
expose_value=False, is_eager=True)
def hello():
click.echo('Hello World!')
expose_value=False 會避免傳遞一個boolean給hello腳本,ctx.resilient_parsing 檢查命令行是否改變執(zhí)行流。
The expose_value parameter prevents the pretty pointless(無謂的) version parameter from being passed to the callback. If that was not specified, a boolean would be passed to the hello script. The resilient_parsing flag is applied to the context if Click wants to parse the command line without any destructive(毀滅性) behavior that would change the execution flow. In this case, because we would exit the program, we instead do nothing.
What it looks like:
$ hello
Hello World!
$ hello --version
Version 1.0
如上所述,這里的 callback 是指 Click.option 的一個參數(shù);
下面這一段將介紹一般性的概念 callback ,與本文主題關(guān)系不大:通過指針來調(diào)用,就是回調(diào)函數(shù)。如果把函數(shù)A的指針p(地址)作為參數(shù)傳遞給另一個函數(shù)B,當(dāng)指針被用來調(diào)用其函數(shù)A時,我們就說發(fā)生了回調(diào)(callback)?;卣{(diào)函數(shù)不是由該函數(shù)的實現(xiàn)方直接調(diào)用,而是在特定的事件發(fā)生時由另外的一方調(diào)用,用于對該事件進(jìn)行響應(yīng)。
o_回調(diào).JPG
從應(yīng)用的層面完全看不出來這和普通的調(diào)用(call)有什么區(qū)別,那為什么還要叫callback呢?我猜是因為實現(xiàn)原理與call有所不同,或者純粹是系統(tǒng)(OS、編譯器、解釋器)給出的概念(參考文)callback.jpg
Yes Parameters#
對于一些危險的命令,我們需要讓用戶再次確認(rèn)。這可以給命令加上一個 --yes flag 并在 callback 中向用戶要求確認(rèn),否則就中止該命令腳本。
...and asking for confirmation if the user did not provide it and to fail in a callback:
def abort_if_false(ctx, param, value):
if not value:
ctx.abort()
@click.command()
@click.option('--yes', is_flag=True, callback=abort_if_false,
expose_value=False,
prompt='Are you sure you want to drop the db?')
def dropdb():
click.echo('Dropped all tables!')
效果如下:
$ dropdb
Are you sure you want to drop the db? [y/N]: n
Aborted!
$ dropdb --yes
Dropped all tables!
還可以使用 confirmation_option() :
@click.command()
@click.confirmation_option(prompt='Are you sure you want to drop the db?')
def dropdb():
click.echo('Dropped all tables!')
Commands and Groups
Click可以任意構(gòu)造命令行程序,這是由...來實現(xiàn)的。
The most important feature of Click is the concept of arbitrarily nesting command line utilities(實用程序). This is implemented through the Command and Group
(actually MultiCommand).
Callback Invocation 回調(diào)請求
對于常規(guī)的命令,callback總是立即執(zhí)行(除非某個參數(shù)阻止了它,比如--help)。
For a regular command, the callback is executed whenever the command runs. If the script is the only command, it will always fire (unless a parameter callback prevents it. This for instance happens if someone passes --help to the script).
但是,用group和multi commands時,只要子命令運(yùn)行了,callback就會被觸發(fā)。
For groups and multi commands, the situation looks different. In this case, the callback fires whenever a subcommand fires (unless this behavior is changed). What this means in practice is that an outer command runs when an inner command runs:
@click.group()
@click.option('--debug/--no-debug', default=False)
def cli(debug):
click.echo('Debug mode is %s' % ('on' if debug else 'off'))
@cli.command()
def sync():
click.echo('Synching')
效果如下:
$ tool.py
Usage: tool.py [OPTIONS] COMMAND [ARGS]...
Options:
--debug / --no-debug
--help Show this message and exit.
Commands:
sync
$ tool.py --debug sync
Debug mode is on
Synching
Passing Parameters
Click strictly separates parameters between commands and subcommands. What this means is that options and arguments for a specific command have to be specified after the command name itself, but before any other command names.
This behavior is already observable with the predefined --help option. Suppose we have a program called tool.py, containing a subcommand called sub.
- tool.py --help will return the help for the whole program (listing subcommands).
- tool.py sub --help will return the help for the sub subcommand.
- But tool.py --help sub will treat --help as an argument for the main program. Click then invokes the callback for --help, which prints the help and aborts the program before click can process the subcommand.
Nested Handling and Contexts #
前面的例子 $ tool.py --debug sync 中,group(即 tool )接受一個debug參數(shù)傳給了它的callback,而 sync 則需接受自己的參數(shù)。
As you can see from the earlier example, the basic command group accepts a debug argument which is passed to its callback, but not to the sync command itself. The sync command only accepts its own arguments.
This allows tools to act completely independent of each other, but how does one command talk to a nested one? The answer to this is the Context.
每次一個命令被喚起時,就會產(chǎn)生一個新的 context對象(上下文環(huán)境)。這些 context對象 會隨值一起、自動地傳給 callback參數(shù),但命令也能主動請求 context對象,只需用一個 pass_context() 裝飾器即可——此時,context 是被裝飾函數(shù)的第一個參數(shù)。
Each time a command is invoked, a new context is created and linked with the parent context. Normally, you can’t see these contexts, but they are there. Contexts are passed to parameter callbacks together with the value automatically. Commands can also ask for the context to be passed by marking themselves with the pass_context() decorator. In that case, the context is passed as first argument.
此 context對象 還能攜帶一個你指定的對象,比如:
The context can also carry a program specified object that can be used for the program’s purposes. What this means is that you can build a script like this:
@click.group()
@click.option('--debug/--no-debug', default=False)
@click.pass_context
def cli(ctx, debug):
ctx.obj['DEBUG'] = debug # obj – an arbitrary object of user data.
@cli.command()
@click.pass_context
def sync(ctx):
click.echo('Debug is %s' % (ctx.obj['DEBUG'] and 'on' or 'off'))
if __name__ == '__main__':
cli(obj={})
一級級的向下傳遞、覆蓋,
If the object is provided, each context will pass the object onwards(向前) to its children, but at any level a context’s object can be overridden. To reach to a parent, context.parent can be used.
除此以外,沒有什么能阻止應(yīng)用程序修改全局狀態(tài)。???
In addition to that, instead of passing an object down, nothing stops the application from modifying global state. For instance, you could just flip(快速翻動) a global DEBUG variable and be done with it.
Decorating Commands #
事實上,callback 是由 Context.invoke() 來調(diào)用的。
As you have seen in the earlier example, a decorator can change how a command is invoked. What actually happens behind the scenes is that callbacks are always invoked through the Context.invoke() method which automatically invokes a command correctly (by either passing the context or not).
直接看示例吧
This is very useful when you want to write custom decorators. For instance, a common pattern would be to configure an object representing state and then storing it on the context and then to use a custom decorator to find the most recent object of this sort and pass it as first argument.
from functools import update_wrapper
def pass_obj(f):
@click.pass_context
def new_func(ctx, *args, **kwargs):
return ctx.invoke(f, ctx.obj, *args, **kwargs)
return update_wrapper(new_func, f)
The Context.invoke() command will automatically invoke the function in the correct way, so the function will either be called with f(ctx, obj) or f(obj) depending on whether or not it itself is decorated with pass_context().
This is a very powerful concept that can be used to build very complex nested applications; see Complex Applications for more information.
