Skip to content

Commit 74ec3e8

Browse files
committed
新增“Python 命令行之旅:深入 click(四)”
1 parent bd93335 commit 74ec3e8

File tree

2 files changed

+301
-3
lines changed

2 files changed

+301
-3
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@
2525
- 2.3 [使用 docopt 实现 git 命令](contents/Python/cmdline/docopt-3.md)
2626
- 3.1 [初探 click](contents/Python/cmdline/click-1.md)
2727
- 3.2 [深入 click(一)](contents/Python/cmdline/click-2.md)
28-
- 3.2 [深入 click(二)](contents/Python/cmdline/click-3.md)
29-
- 3.3 [深入 click(三)](contents/Python/cmdline/click-4.md)
28+
- 3.3 [深入 click(二)](contents/Python/cmdline/click-3.md)
29+
- 3.4 [深入 click(三)](contents/Python/cmdline/click-4.md)
30+
- 3.5 [深入 click(四)](contents/Python/cmdline/click-5.md)
3031
2. [用 Python 生成有“灵魂”的二维码:QRcode](contents/Python/QRcode/content.md)
3132
3. 聊聊 Python 的单元测试框架
3233
- 3.1 [unittest](contents/Python/unittest/unittest.md)
@@ -40,7 +41,6 @@
4041
3. [超级好用的 Java 数据可视化库:Tablesaw](contents/Java/tablesaw/content.md)
4142
4. [快速搭建 SpringCloud 微服务开发环境的脚手架](contents/Java/SpringCloud/content.md)
4243

43-
4444
#### JS 系列
4545

4646
1. [功能强大的 JS 文件上传库:FilePond](contents/JavaScript/FilePond/content.md)

contents/Python/cmdline/click-5.md

Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
# Python 命令行之旅:深入 click(四)
2+
3+
## 一、前言
4+
5+
在前面三篇文章中,我们介绍了 `click` 中的参数、选项和命令,本文将介绍 `click` 锦上添花的功能,以帮助我们更加轻松地打造一个更加强大的命令行程序。
6+
7+
```
8+
本系列文章默认使用 Python 3 作为解释器进行讲解。
9+
若你仍在使用 Python 2,请注意两者之间语法和库的使用差异哦~
10+
```
11+
12+
## 二、增强功能
13+
14+
### 2.1 Bash 补全
15+
16+
Bash 补全是 `click` 提供的一个非常便捷和强大的功能,这是它比 `argpase``docopt` 强大的一个表现。
17+
18+
在命令行程序正确安装后,Bash 补全才可以使用。而如何安装可以参考 [setup 集成](https://click.palletsprojects.com/en/7.x/setuptools/#setuptools-integration)。Click 目前仅支持 Bash 和 Zsh 的补全。
19+
20+
#### 2.1.1 补全能力
21+
22+
通常来说,Bash 补全支持对子命令、选项、以及选项或参数值得补全。比如:
23+
24+
```plain
25+
$ repo <TAB><TAB>
26+
clone commit copy delete setuser
27+
$ repo clone -<TAB><TAB>
28+
--deep --help --rev --shallow -r
29+
```
30+
31+
此外,`click` 还支持自定义补全,这在动态生成补全场景中很有用,使用 `autocompletion` 参数。`autocompletion` 需要指定为一个回调函数,并且返回字符串的列表。此函数接受三个参数:
32+
33+
- `ctx` —— 当前的 click 上下文
34+
- `args` 传入的参数列表
35+
- `incomplete` 正在补全的词
36+
37+
这里有一个根据环境变量动态生成补全的示例:
38+
39+
```python
40+
import os
41+
42+
def get_env_vars(ctx, args, incomplete):
43+
return [k for k in os.environ.keys() if incomplete in k]
44+
45+
@click.command()
46+
@click.argument("envvar", type=click.STRING, autocompletion=get_env_vars)
47+
def cmd1(envvar):
48+
click.echo('Environment variable: %s' % envvar)
49+
click.echo('Value: %s' % os.environ[envvar])
50+
```
51+
52+
`ZSH` 中,还支持补全帮助信息。只需将 `autocompletion` 回调函数中返回的字符串列表中的字符串改为二元元组,第一个元素是补全内容,第二个元素是帮助信息。
53+
54+
这里有一个颜色补全的示例:
55+
56+
```python
57+
import os
58+
59+
def get_colors(ctx, args, incomplete):
60+
colors = [('red', 'help string for the color red'),
61+
('blue', 'help string for the color blue'),
62+
('green', 'help string for the color green')]
63+
return [c for c in colors if incomplete in c[0]]
64+
65+
@click.command()
66+
@click.argument("color", type=click.STRING, autocompletion=get_colors)
67+
def cmd1(color):
68+
click.echo('Chosen color is %s' % color)
69+
```
70+
71+
#### 2.1.2 激活补全
72+
73+
要激活 Bash 的补全功能,就需要告诉它你的命令行程序有补全的能力。通常通过一个神奇的环境变量 `_<PROG_NAME>_COMPLETE` 来告知,其中 `<PROG_NAME>` 是大写下划线形式的程序名称。
74+
75+
比如有一个命令行程序叫做 `foo-bar`,那么对应的环境变量名称为 `_FOO_BAR_COMPLETE`,然后在 `.bashrc` 中使用 `source` 导出即可:
76+
77+
````bash
78+
eval "$(_FOO_BAR_COMPLETE=source foo-bar)"
79+
···
80+
81+
或者在 `.zshrc` 中使用:
82+
```bash
83+
eval "$(_FOO_BAR_COMPLETE=source_zsh foo-bar)"
84+
````
85+
86+
不过上面的方式总是在命令行程序启动时调用,这可能在有多个程序时减慢 shell 激活的速度。另一种方式是把命令放在文件中,就像这样:
87+
88+
```bash
89+
# 针对 Bash
90+
_FOO_BAR_COMPLETE=source foo-bar > foo-bar-complete.sh
91+
92+
# 针对 ZSH
93+
_FOO_BAR_COMPLETE=source_zsh foo-bar > foo-bar-complete.sh
94+
```
95+
96+
然后把脚本文件路径加到 `.bashrc``.zshrc` 中:
97+
98+
```bash
99+
. /path/to/foo-bar-complete.sh
100+
```
101+
102+
### 2.2 实用工具
103+
104+
#### 2.2.1 打印到标准输出
105+
106+
[echo()](https://click.palletsprojects.com/en/7.x/api/#click.echo) 函数可以说是最有用的实用工具了。它和 Python 的 `print` 类似,主要的区别在于它同时在 Python 2 和 3 中生效,能够智能地检测未配置正确的输出流,且几乎不会失败(除了 Python 3 中的[少数限制](https://click.palletsprojects.com/en/7.x/python3/#python3-limitations)。)
107+
108+
`echo` 即支持 unicode,也支持二级制数据,如:
109+
110+
```python
111+
import click
112+
113+
click.echo('Hello World!')
114+
115+
click.echo(b'\xe2\x98\x83', nl=False) # nl=False 表示不输出换行符
116+
```
117+
118+
#### 2.2.2 ANSI 颜色
119+
120+
有些时候你可能希望输出是有颜色的,这尤其在输出错误信息时有用,而 `click` 在这方面支持的很好。
121+
122+
首先,你需要安装 `colorama`
123+
124+
```bash
125+
pip install colorama
126+
```
127+
128+
然后,就可以使用 [style()](https://click.palletsprojects.com/en/7.x/api/#click.style) 函数来指定颜色:
129+
130+
```python
131+
import click
132+
133+
click.echo(click.style('Hello World!', fg='green'))
134+
click.echo(click.style('Some more text', bg='blue', fg='white'))
135+
click.echo(click.style('ATTENTION', blink=True, bold=True))
136+
```
137+
138+
`click` 还提供了更加简便的函数 [secho](https://click.palletsprojects.com/en/7.x/api/#click.secho),它就是 `echo` 和 `style` 的组合:
139+
140+
```python
141+
click.secho('Hello World!', fg='green')
142+
click.secho('Some more text', bg='blue', fg='white')
143+
click.secho('ATTENTION', blink=True, bold=True)
144+
```
145+
146+
#### 2.2.3 分页支持
147+
148+
有些时候,命令行程序会输出长文本,但你希望能让用户盘也浏览。使用 [echo_via_pager()](https://click.palletsprojects.com/en/7.x/api/#click.echo_via_pager) 函数就可以轻松做到。
149+
150+
例如:
151+
152+
```python
153+
def less():
154+
click.echo_via_pager('\n'.join('Line %d' % idx
155+
for idx in range(200)))
156+
```
157+
158+
如果输出的文本特别大,处于性能的考虑,希望翻页时生成对应内容,那么就可以使用生成器:
159+
160+
```python
161+
def _generate_output():
162+
for idx in range(50000):
163+
yield "Line %d\n" % idx
164+
165+
@click.command()
166+
def less():
167+
click.echo_via_pager(_generate_output())
168+
```
169+
170+
#### 2.2.4 清除屏幕
171+
172+
使用 [clear()](https://click.palletsprojects.com/en/7.x/api/#click.clear) 可以轻松清除屏幕内容:
173+
174+
```python
175+
import click
176+
click.clear()
177+
```
178+
179+
#### 2.2.5 从终端获取字符
180+
181+
通常情况下,使用内建函数 `input``raw_input` 获得的输入是用户输出一段字符然后回车得到的。但在有些场景下,你可能想在用户输入单个字符时就能获取到并且做一定的处理,这个时候 [getchar()](https://click.palletsprojects.com/en/7.x/api/#click.getchar) 就派上了用场。
182+
183+
比如,根据输入的 `y``n` 做特定处理:
184+
185+
```python
186+
import click
187+
188+
click.echo('Continue? [yn] ', nl=False)
189+
c = click.getchar()
190+
click.echo()
191+
if c == 'y':
192+
click.echo('We will go on')
193+
elif c == 'n':
194+
click.echo('Abort!')
195+
else:
196+
click.echo('Invalid input :(')
197+
```
198+
199+
#### 2.2.6 等待按键
200+
201+
在 Windows 的 cmd 中我们经常看到当执行完一个命令后,提示按下任意键退出。通过使用 [pause()](https://click.palletsprojects.com/en/7.x/api/#click.pause) 可以实现暂停直至用户按下任意键:
202+
203+
```python
204+
import click
205+
click.pause()
206+
```
207+
208+
#### 2.2.7 启动编辑器
209+
210+
通过 [edit()](https://click.palletsprojects.com/en/7.x/api/#click.edit) 可以自动启动编辑器。这在需要用户输入多行内容时十分有用。
211+
212+
在下面的示例中,会启动默认的文本编辑器,并在里面输入一段话:
213+
214+
```python
215+
import click
216+
217+
def get_commit_message():
218+
MARKER = '# Everything below is ignored\n'
219+
message = click.edit('\n\n' + MARKER)
220+
if message is not None:
221+
return message.split(MARKER, 1)[0].rstrip('\n')
222+
```
223+
224+
`edit()` 函数还支持打开特定文件,比如:
225+
226+
```python
227+
import click
228+
click.edit(filename='/etc/passwd')
229+
```
230+
231+
#### 2.2.8 启动应用程序
232+
233+
通过 [launch](https://click.palletsprojects.com/en/7.x/api/#click.launch) 可以打开 URL 或文件类型所关联的默认应用程序。如果设置 `locate=True`,则可以启动文件管理器并自动选中特定文件。
234+
235+
示例:
236+
237+
```python
238+
# 打开浏览器,访问 URL
239+
click.launch("https://click.palletsprojects.com/")
240+
241+
# 使用默认应用程序打开 txt 文件
242+
click.launch("/my/downloaded/file.txt")
243+
244+
# 打开文件管理器,并自动选中 file.txt
245+
click.launch("/my/downloaded/file.txt", locate=True)
246+
```
247+
248+
#### 2.2.9 显示进度条
249+
250+
`click` 内置了 [progressbar()](https://click.palletsprojects.com/en/7.x/api/#click.progressbar) 函数来方便地显示进度条。
251+
252+
它的用法也很简单,假定你有一个要处理的可迭代对象,处理完每一项就要输出一下进度,那么就有两种用法。
253+
254+
用法一:使用 `progressbar` 构造出 `bar` 对象,迭代 `bar` 对象来自动告知进度:
255+
256+
```python
257+
import time
258+
import click
259+
260+
all_the_users_to_process = ['a', 'b', 'c']
261+
262+
def modify_the_user(user):
263+
time.sleep(0.5)
264+
265+
with click.progressbar(all_the_users_to_process) as bar:
266+
for user in bar:
267+
modify_the_user(user)
268+
```
269+
270+
用法二:使用 `progressbar` 构造出 `bar` 对象,迭代原始可迭代对象,并不断向 `bar` 更新进度:
271+
272+
```python
273+
import time
274+
import click
275+
276+
all_the_users_to_process = ['a', 'b', 'c']
277+
278+
def modify_the_user(user):
279+
time.sleep(0.5)
280+
281+
with click.progressbar(all_the_users_to_process) as bar:
282+
for user in enumerate(all_the_users_to_process):
283+
modify_the_user(user)
284+
bar.update(1)
285+
```
286+
287+
#### 2.2.10 更多实用工具
288+
289+
- [打印文件名](https://click.palletsprojects.com/en/7.x/utils/#printing-filenames)
290+
- [标准流](https://click.palletsprojects.com/en/7.x/utils/#standard-streams)
291+
- [智能打开文件](https://click.palletsprojects.com/en/7.x/utils/#intelligent-file-opening)
292+
- [查找应用程序文件夹](https://click.palletsprojects.com/en/7.x/utils/#finding-application-folders)
293+
294+
## 三、总结
295+
296+
`click` 提供了非常多的增强型功能,本文着重介绍了它的 Bash 补全和十多个实用工具,这会让你在实现命令行的过程中如虎添翼。此外,`click` 还提供了诸如命令别名、参数修改、标准化令牌、调用其他命令、回调顺序等诸多[高级模式](https://click.palletsprojects.com/en/7.x/advanced/) 以应对更加复杂或特定的场景,我们就不再深入介绍。
297+
298+
`click` 的介绍就告一段落,它将会是你编写命令行程序的一大利器。在下一篇文章中,我们依然会通过实现一个简单的 `git` 程序来进行 `click` 的实战。

0 commit comments

Comments
 (0)