Skip to content

Commit 71481eb

Browse files
committed
新增“Python 命令行之旅:深入 fire(二)”
1 parent 6b67a93 commit 71481eb

File tree

2 files changed

+305
-0
lines changed

2 files changed

+305
-0
lines changed

Diff for: README.md

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
- 3.6 [使用 click 实现 git 命令](contents/Python/cmdline/click-6.md)
3232
- 4.1 [初探 fire](contents/Python/cmdline/fire-1.md)
3333
- 4.2 [Python 命令行之旅:深入 fire(一)](contents/Python/cmdline/fire-2.md)
34+
- 4.3 [Python 命令行之旅:深入 fire(二)](contents/Python/cmdline/fire-3.md)
3435
2. [用 Python 生成有“灵魂”的二维码:QRcode](contents/Python/QRcode/content.md)
3536
3. 聊聊 Python 的单元测试框架
3637
- 3.1 [unittest](contents/Python/unittest/unittest.md)

Diff for: contents/Python/cmdline/fire-3.md

+304
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
# Python 命令行之旅:深入 fire(二)
2+
3+
## 一、前言
4+
5+
在上一篇文章中我们介绍了 `fire` 的子命令、嵌套命令和属性访问等内容,今天我们将继续深入了解 `fire` 的其他功能。
6+
7+
```
8+
本系列文章默认使用 Python 3 作为解释器进行讲解。
9+
若你仍在使用 Python 2,请注意两者之间语法和库的使用差异哦~
10+
```
11+
12+
## 二、功能
13+
14+
### 2.1 最简命令实现
15+
16+
在上一节中,我们介绍了只要定义一个函数就可以实现命令行程序。比如:
17+
18+
```python
19+
import fire
20+
21+
def english():
22+
return 'Hello, fire!'
23+
24+
def chinese():
25+
return '你好,fire!'
26+
27+
if __name__ == '__main__':
28+
fire.Fire()
29+
```
30+
31+
但这还不是最简单的实现方式,`fire` 甚至允许你通过定义变量的方式来实现命令行!
32+
上面的例子可以写成下面这种形式:
33+
34+
```python
35+
import fire
36+
37+
english = 'Hello, fire!'
38+
chinese = '你好,fire!'
39+
40+
if __name__ == '__main__':
41+
fire.Fire()
42+
```
43+
44+
### 2.2 链式调用
45+
46+
`Fire CLI` 中,你可以通过链式调用不断地对上一个结果进行处理。
47+
48+
想做到这一点也很简单,就是在实例方法中返回 `self` 即可。
49+
50+
在下面的示例中,我们实现了一个简单的四则运算命令,可链式调用 `add``sub``mul``div`
51+
52+
```python
53+
import fire
54+
55+
class Calculator:
56+
57+
def __init__(self):
58+
self.result = 0
59+
self.express = '0'
60+
61+
def __str__(self):
62+
return f'{self.express} = {self.result}'
63+
64+
def add(self, x):
65+
self.result += x
66+
self.express = f'{self.express}+{x}'
67+
return self
68+
69+
def sub(self, x):
70+
self.result -= x
71+
self.express = f'{self.express}-{x}'
72+
return self
73+
74+
def mul(self, x):
75+
self.result *= x
76+
self.express = f'({self.express})*{x}'
77+
return self
78+
79+
def div(self, x):
80+
self.result /= x
81+
self.express = f'({self.express})/{x}'
82+
return self
83+
84+
if __name__ == '__main__':
85+
fire.Fire(Calculator)
86+
```
87+
88+
上述代码中的 `add``sub``mul``div` 分别对应加、减、乘、除的逻辑,每个方法都接受 `x` 参数作为参与运算的数字,返回值均为 `self`,这样就可以无限次地链式调用。在命令行中链式调用结束后,会最终调用到 `__str__` 方法将结果打印出来。
89+
90+
其中,`__str__``fire` 中用来完成自定义序列化。如果不提供这个方法,在链式调用完成后将会打印帮助内容。
91+
92+
比如,我们可以这么调用:
93+
94+
```bash
95+
$ python calculator.py add 1 sub 2 mul 3 div 4
96+
((+1-2)*3)/4 = -0.75
97+
98+
$ python calculator.py add 1 sub 2 mul 3 div 4 add 4 sub 3 mul 2 div 1
99+
((((0+1-2)*3)/4+4-3)*2)/1 = 0.5
100+
```
101+
102+
### 2.3 位置参数和选项参数
103+
104+
通过前面的介绍我们也都清楚了在 `fire` 中不必显式的定义位置参数或选项参数。
105+
106+
通过下面的例子,我们将细化两类参数的使用:
107+
108+
```python
109+
import fire
110+
111+
class Building(object):
112+
113+
def __init__(self, name, stories=1):
114+
self.name = name
115+
self.stories = stories
116+
117+
def __str__(self):
118+
return f'name: {self.name}, stories: {self.stories}'
119+
120+
def climb_stairs(self, stairs_per_story=10):
121+
yield self.name
122+
for story in range(self.stories):
123+
for stair in range(1, stairs_per_story):
124+
yield stair
125+
yield 'Phew!'
126+
yield 'Done!'
127+
128+
if __name__ == '__main__':
129+
fire.Fire(Building)
130+
```
131+
132+
- 构造函数中定义的参数(如 `name` 和 `stories`)在命令行中仅为选项参数(如 `--name` 和 `--stories`)。我们可以这么调用:
133+
134+
```bash
135+
$ python example.py --name="Sherrerd Hall" --stories=3
136+
```
137+
138+
- 构造函数中定义的参数可在命令中放于任意位置。比如下面两个调用都是可以的:
139+
140+
```bash
141+
$ python example.py --name="Sherrerd Hall" climb-stairs --stairs-per-story 10
142+
$ python example.py climb-stairs --stairs-per-story 10 --name="Sherrerd Hall"
143+
```
144+
145+
- 构造函数和普通方法中定义的默认参数(如 `stories`),在命令行中是可选的。我们可以这么调用:
146+
147+
```bash
148+
$ python example.py --name="Sherrerd Hall"
149+
```
150+
151+
- 普通方法中定义的参数(如 `stairs_per_story`)在命令行中即可以是位置参数,也可以是选项参数。我们可以这么调用:
152+
153+
```bash
154+
# 作为位置参数
155+
$ python example.py --name="Sherrerd Hall" climb_stairs 10
156+
# 作为选项参数
157+
$ python example.py --name="Sherrerd Hall" climb_stairs --stairs_per_story=10
158+
```
159+
160+
- 选项参数中的横杠(`-`)和下划线(`_`)是等价的。因此也可以这么调用:
161+
162+
```bash
163+
# 作为选项参数
164+
$ python example.py --name="Sherrerd Hall" climb_stairs --stairs-per-story=10
165+
```
166+
167+
此外,`fire` 还支持在函数中定义 `*args` 和 `**kwargs`。
168+
169+
```python
170+
import fire
171+
172+
def fargs(*args):
173+
return str(args)
174+
175+
176+
def fkwargs(**kwargs):
177+
return str(kwargs)
178+
179+
if __name__ == '__main__':
180+
fire.Fire()
181+
```
182+
183+
- 函数中的 `*args` 在命令行中为位置参数。我们可以这么调用:
184+
185+
```bash
186+
$ python example.py fargs a b c
187+
```
188+
189+
- 函数中的 `**kwargs` 在命令行中为选项参数。我们可以这么调用:
190+
191+
```bash
192+
$ python example.py fargs --a a1 --b b1 --c c1
193+
```
194+
195+
- 通过分隔符 `-` 可显式告知分隔符后的为子命令,而非命令的参数。且看下面的示例:
196+
197+
```bash
198+
# 没有使用分隔符,upper 被作为位置参数
199+
$ python example.py fargs a b c upper
200+
('a', 'b', 'c', 'upper')
201+
202+
# 使用了分隔符,upper 被作为子命令
203+
$ python example.py fargs a b c - upper
204+
('A', 'B', 'C')
205+
```
206+
207+
- 通过 `fire` 内置的 `--separator` 可以自定义分隔符,此选项参数需要跟在单独的 `--` 后面:
208+
209+
```bash
210+
$ python example.py a b c X upper -- --separator=X
211+
('A', 'B', 'C')
212+
```
213+
214+
### 2.4 参数类型
215+
216+
在 `fire` 中,参数的类型由其值决定,通过下面的简单代码,我们可以看到给不同的值时,`fire`会解析为什么类型:
217+
218+
```python
219+
import fire
220+
fire.Fire(lambda obj: type(obj).__name__)
221+
```
222+
223+
```bash
224+
$ python example.py 10
225+
int
226+
$ python example.py 10.0
227+
float
228+
$ python example.py hello
229+
str
230+
$ python example.py '(1,2)'
231+
tuple
232+
$ python example.py [1,2]
233+
list
234+
$ python example.py True
235+
bool
236+
$ python example.py {name: David}
237+
dict
238+
```
239+
240+
如果想传递字符串形式的数字,那就需要小心引号了,要么把引号引起来,要么转义引号:
241+
242+
```bash
243+
# 数字 10
244+
$ python example.py 10
245+
int
246+
# 没有对引号处理,仍然是数字10
247+
$ python example.py "10"
248+
int
249+
# 把引号引起来,所以是字符串“10
250+
$ python example.py '"10"'
251+
str
252+
# 另一种把引号引起来的形式
253+
$ python example.py "'10'"
254+
str
255+
# 转义引号
256+
$ python example.py \"10\"
257+
str
258+
```
259+
260+
考虑下更复杂的场景,如果传递的是字典,在字典中有字符串,那么也是要小心引号的:
261+
262+
```bash
263+
# 推荐做法
264+
$ python example.py '{"name": "David Bieber"}'
265+
dict
266+
# 也是可以的
267+
$ python example.py {"name":'"David Bieber"'}
268+
dict
269+
# 错误,会被解析为字符串
270+
$ python example.py {"name":"David Bieber"}
271+
str
272+
# 错误,不会作为单个参数(因为中间有空格),报错
273+
$ python example.py {"name": "David Bieber"}
274+
<error>
275+
```
276+
277+
如果值为 `True` 或 `False` 将为视为布尔值,`fire` 还支持通过 `--name` 将 `name` 设为 `True`,或通过 `--noname` 将 `name` 设为 `False`:
278+
279+
```bash
280+
$ python example.py --obj=True
281+
bool
282+
$ python example.py --obj=False
283+
bool
284+
$ python example.py --obj
285+
bool
286+
$ python example.py --noobj
287+
bool
288+
```
289+
290+
### 2.5 Fire 内置选项参数
291+
292+
Fire 内置了一些选项参数,以帮助我们更容易地使用命令行程序。若想使用内置的选项功能,需要将选项参数跟在 `--` 后,在上文中,我们介绍了 `--separator` 参数,除了它,`fire` 还支持以下选项参数:
293+
294+
- `command -- --help` 列出详细的帮助信息
295+
- `command -- --interactive` 进入交互式模式
296+
- `command -- --completion [shell]` 生成 CLI 程序的自动补全脚本,以支持自动补全
297+
- `command -- --trace` 获取命令的 Fire 追踪以了解调用 Fire 后究竟发生了什么
298+
- `command -- --verbose` 获取包含私有成员在内的详情
299+
300+
## 三、小结
301+
302+
`fire` 让命令行程序的实现变得特别简单,本文着重介绍了它的链式调用、选项参数、位置参数、参数类型以及内置选项参数。`fire` 的概念并不多,真正践行了“把简单留给他人,把复杂留给自己”的理念。
303+
304+
`fire` 的介绍就告一段落,它绝对会是你编写命令行程序的一大利器。在下一篇文章中,我们依然会通过实现一个简单的 `git` 程序来进行 `fire` 的实战。

0 commit comments

Comments
 (0)