Python 用 Click 模組製作好用的指令
Last updated on Feb 13, 2024 in Python 模組/套件推薦 by Amo Chen ‐ 5 min read
Python 作為一個成熟的腳本語言(scripting langage),已經發展相當多方便地模組、套件供開發者們使用,得益於其方便性與易用性, Python 也十分適合用來撰寫命令列(command line)工具,幫忙解決日常工作或生活上瑣事的自動化。
本文將介紹 Click 模組,讓開發者們能夠更方便地製作命令列工具。
學會 Click 後就能將各種製作指令的瑣事交給它 ,將能夠更專注於主要功能的開發!
本文環境
- Python 3.11
- Click 8.1
$ pip install click
Hello, Click
首先看看如何用 Click 製作第 1 個指令 ( hello.py ):
import click
@click.command()
@click.option('-t', '--to', 'to', help='To who')
def greeting(to):
'''Say hello to someone'''
print(f'Hello, {to or "stranger"}!')
if __name__ == '__main__':
greeting()
上述指令的執行結果:
$ python hello.py
Hello, stranger!
如果加上 --help
就會發現漂亮的命令列說明,連參數說明也有:
$ python hello.py --help
Usage: hello.py [OPTIONS]
Say hello to someone
Options:
-t, --to TEXT To who
--help Show this message and exit.
接著加上 -t
參數試試:
$ python hello.py -t Michael
Hello, Michael!
雖然內建的 argparse 模組已提供製作命令列工具會需要的功能,但使用上還是有些許不方便的地方,並不像 Click 可以直接用裝飾子(decorator)的方式製作命令列工具,或是像 Click 提供不少 options 可供使用,總的來說, Click 方便性無話可說,一定是大拇指的啦!
而 Flask 也採用 Click 作為其製作指令(command)的功能 (詳見 文件 ) 需要使用的模組,因此在穩定性方面有一定程度的保證。
必要參數
第 1 個範例可以清楚看到透過 @click.option
能夠添加命令列的參數,然而 option 預設並不是必要的參數,如要改為必要的參數,可以在 @click.option
代入 required=True
:
import click
@click.command()
@click.option('-t', '--to', 'to', help='To who', required=True)
def greeting(to):
'''Say hello to someone'''
print(f'Hello, {to or "stranger"}!')
if __name__ == '__main__':
greeting()
如此一來,指令的說明也會出現 [required]
的備註:
$ python hello.py --help
Usage: hello.py [OPTIONS]
Say hello to someone
Options:
-t, --to TEXT To who [required]
--help Show this message and exit.
如果執行時未指定必要參數,也會提示必要參數必須指定:
$ python hello.py
Usage: hello.py [OPTIONS]
Try "hello.py --help" for help.
Error: Missing option "-t" / "--to".
參數預設值
在上述範例中用 f-string 在未指定 -t / --to
參數時用 stranger
作為預設值:
{to or "stranger"}
此時同樣可以透過 Click 設定預設值,只要代入 default=<預設值>
與 show_default=True
(將預設值顯示在 help 說明中)即可:
import click
@click.command()
@click.option('-t', '--to', 'to', help='To who', default='stranger', show_default=True)
def greeting(to):
'''Say hello to someone'''
print(f'Hello, {to}')
if __name__ == '__main__':
greeting()
執行 --help
時就會發現說明中特別標示預設值是什麼:
$ python hello.py --help
Usage: hello.py [OPTIONS]
Say hello to someone
Options:
-t, --to TEXT To who [default: stranger]
--help Show this message and exit.
驗證參數
驗證參數是命令列指令開發不可或缺的一環,例如檢查參數是否為字串、整數、日期等,目前 Click 提供多種型別的檢查:
str
int
float
bool
click.uuid
click.File
click.Path
click.Choice
click.IntRange
click.FloatRange
click.DateTime
各種型別的說明詳見 Parameter Types 。
以下示範如何使用這些型別,只要額外代入 type=<type>
參數即可:
import click
@click.command()
@click.option('-t', '--to', 'to', help='To who', default='stranger', show_default=True)
@click.option('-r', '--repeat', 'repeat', help='Repeat n times', type=int, required=True)
def greeting(to, repeat):
'''Say hello to someone'''
for __ in range(0, repeat):
print(f'Hello, {to}')
if __name__ == '__main__':
greeting()
執行 --help
時,指令說明也同時顯示接受何種型別的參數:
python hello2.py --help
Usage: hello2.py [OPTIONS]
Say hello to someone
Options:
-t, --to TEXT To who [default: stranger]
-r, --repeat INTEGER Repeat n times [required]
--help Show this message and exit.
如果故意使用錯誤型別,也會無法通過檢查:
python hello.py -r abc
Usage: hello.py [OPTIONS]
Try "hello.py --help" for help.
Error: Invalid value for "-r" / "--repeat": abc is not a valid integer
客製型別(Custom Types)
Click 也能夠透過繼承 click.ParamType
類別達到客製化型別的目的,只要實作 convert
方法即可,例如以下實作奇數型別,讓 --repeat
參數只接受奇數:
import click
class OddIntType(click.ParamType):
name = 'odd_int'
def convert(self, value, param, ctx):
try:
n = int(value)
if n % 2 == 0:
raise ValueError
return n
except ValueError:
self.fail('%s is not a valid odd integer' % value, param, ctx)
ODD_INT = OddIntType()
@click.command()
@click.option('-t', '--to', 'to', help='To who', default='stranger', show_default=True)
@click.option('-r', '--repeat', 'repeat', help='Repeat n times', type=ODD_INT, required=True)
def greeting(to, repeat):
'''Say hello to someone'''
for __ in range(0, repeat):
print(f'Hello, {to}')
if __name__ == '__main__':
greeting()
如果 --repeat
故意代入偶數就會無法通過 OddIntType
的檢查:
$ python hello.py -r 2
Usage: hello.py [OPTIONS]
Try "hello.py --help" for help.
Error: Invalid value for "-r" / "--repeat": 2 is not a valid odd integer
多值參數(Multi Value Options)
有時候一個參數可能需要多個值,例如計算總和就是這種情境。 Click 模組也提供此種多值參數的用法。
需要注意的是 Click 目前提供 @click.option
與 @click.argument
2 種製作參數的方式,然而 @click.option
所能接受的多值參數是有限個數的,例如:
import click
@click.command()
@click.option('-s', '--sum', 'numbers', nargs=2, required=True, type=float)
def do_sum(numbers):
print(f'sum: {sum(numbers)}')
if __name__ == '__main__':
do_sum()
上述範例利用 nargs=2
指定 --sum
必須 2 個值,如果少指定一個就會出現錯誤提示:
$ python do_sum.py -s 1
Error: -s option requires 2 arguments
如果超過指定的 2 個參數也同樣會出現錯誤:
python do_sum.py -s 1 2 4
Usage: di_sum.py [OPTIONS]
Try "do_sum.py --help" for help.
Error: Got unexpected extra argument (4)
如果想要擁有不定參數(Variadic Arguments)的用法,就只能使用 @click.argument
:
import click
@click.command()
@click.argument('numbers', nargs=-1, required=True, type=float)
def do_sum(numbers):
print(f'sum: {sum(numbers)}')
if __name__ == '__main__':
do_sum()
上述指定 nargs=-1
就會讓 numbers
支援不定參數:
$ python do_sum.py 1 2 3 4
sum: 10.0
在此趁機說明 @click.argument
是 positional 形式的參數,會依照參數位置自動對應到不同的參數:
import click
@click.command()
@click.argument('a', required=True)
@click.argument('b', required=True)
def p(a, b):
print(f'a: {a}')
print(f'b: {b}')
if __name__ == '__main__':
p()
因此上述的範例不需要額外指定 -a
, -b
,而是透過參數位置自動對應到 a 與 b ,參數的位置可以用 --help
查看:
$ python pritnab.py --help
Usage: pritnab.py.py [OPTIONS] A B
Options:
--help Show this message and exit.
直接執行以下指令,就會發現 1, 2 分別會被自動對應到 a 與 b :
$ python pritnab.py 1 2
a: 1
b: 2
這就是 argument 與 option 不同的地方。
Prompting
有時候命令列指令還需要詢問使用者的相關資訊, Click 也提供相當方便的 prompt 參數供我們使用,同時 prompt 也支援隱藏密碼輸入的功能,例如以下範例同時詢問使用者名稱與密碼:
import click
@click.command()
@click.option('--name', prompt='Your name please', required=True)
@click.option('--password', prompt=True, hide_input=True, confirmation_prompt=True)
def register(name, password):
click.echo('Hello %s!' % name)
if __name__ == '__main__':
register()
執行結果:
$ python register.py
Your name please: Michael
Password:
Repeat for confirmation:
Hello Michael!
如果要隱藏密碼類型的輸入,只需要將代入 prompt=True, hide_input=True
至 @click.option
中即可。
如果需要再次確認,則額外再代入 confirmation_prompt=True
參數至 @click.option
中即可達成目的,相當簡單!
指令群組 (Commands & Groups)
Click 也支援將多個指令放在同 1 群組的功能,如此一來我們就能夠將多個指令放在同 1 個 Python script 中,使用上會更為方便。
import click
@click.group()
@click.option('--debug/--no-debug', default=False)
def cli(debug):
click.echo(f"Debug mode is {'on' if debug else 'off'}")
@cli.command() # @cli, not @click!
def sync():
click.echo('Syncing')
@cli.command()
def stop():
click.echo('Stopped')
if __name__ == '__main__':
cli()
執行上述指令將會出現以下結果,可以看到 Commands:
底下顯示有 2 個指令能夠使用:
$ python group_example.py
Usage: group_example.py [OPTIONS] COMMAND [ARGS]...
Options:
--debug / --no-debug
--help Show this message and exit.
Commands:
stop
sync
如果要使用 stop
指令的話,只要輸入指令:
$ python group_example.py stop
以下是執行結果:
Debug mode is off
Stopped
自動補齊
結語
Click 提供許多製作命令列指令時所需要使用的功能,更提供自動化製作指令說明的功能,同時也整合 Setuptools 讓我們能輕鬆地將指令安裝在系統之中,有效增加開發指令的效率,讓開發者更能專注在核心功能的開發。而 Click 文件中也有許多本文未涵蓋的內容,有興趣的話可以前往好好翻閱,應能夠有不少收穫!
Happy Coding!
References
https://click.palletsprojects.com/en/8.1.x/setuptools/