Skip to content

Commit 6b67a93

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

File tree

2 files changed

+254
-0
lines changed

2 files changed

+254
-0
lines changed

Diff for: README.md

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

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

+253
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
# Python 命令行之旅:深入 fire(一)
2+
3+
## 一、前言
4+
5+
在第一篇“初探 fire”的文章中,我们初步掌握了使用 `fire` 的简单步骤,了解了它 Pythonic 的用法。
6+
7+
今天我们将深入了解 `fire` 的子命令、嵌套命令和属性访问功能。
8+
9+
```
10+
本系列文章默认使用 Python 3 作为解释器进行讲解。
11+
若你仍在使用 Python 2,请注意两者之间语法和库的使用差异哦~
12+
```
13+
14+
## 二、功能
15+
16+
### 2.1 子命令
17+
18+
使用 `fire` 实现子命令有多种方式:
19+
20+
#### 2.1.1 定义若干函数,使用 fire.Fire()
21+
22+
实现子命令最简单的方式就是定义若干个函数,每个函数名隐式就是子命令名称,然后调用 `fire.Fire()` 变将当前模块所有的函数解析为对应的子命令的处理函数。
23+
24+
```python
25+
import fire
26+
27+
def add(x, y):
28+
return x + y
29+
30+
def multiply(x, y):
31+
return x * y
32+
33+
if __name__ == '__main__':
34+
fire.Fire()
35+
```
36+
37+
然后我们就可以在命令行中这么调用:
38+
39+
```bash
40+
$ python example.py add 10 20
41+
30
42+
$ python example.py multiply 10 20
43+
200
44+
```
45+
46+
关于如何识别参数类型,比如上述 `add 10 20``10``20` 是作为数字而非字符串,我们会在下篇文章的参数解析章节中进行讲解。
47+
48+
#### 2.1.2 定义若干函数,使用 fire.Fire(<dict>)
49+
50+
`2.1.1` 的版本中,会把所有函数都当做是子命令。有时我们可能只想把部分函数当做子命令,或者是希望子命令名称和函数名称不一样。这个时候我们就可以通过字典对象显式地告诉 `fire`
51+
52+
字典对象的形式为 `{'子命令名称': 函数}`,比如前面的示例中,我们希望最终的子命令为 `add``mul`,那么就可以这么写:
53+
54+
```python
55+
fire.Fire({
56+
'add': add,
57+
'mul': multiply,
58+
})
59+
```
60+
61+
然后我们就可以在命令行中这么调用:
62+
63+
```bash
64+
$ python example.py add 10 20
65+
30
66+
$ python example.py mul 10 20
67+
200
68+
```
69+
70+
#### 2.1.3 定义类和方法,使用 fire.Fire(<object>)
71+
72+
定义类和方法的这种方式我们在上一篇文章中介绍过,它和定义函数的方式基本相同,只不过是用类的方式来组织。
73+
74+
然后将类实例化,并把实例化的对象多为 `fire.Fire` 的入参:
75+
76+
```python
77+
import fire
78+
79+
class Calculator(object):
80+
81+
def add(self, x, y):
82+
return x + y
83+
84+
def multiply(self, x, y):
85+
return x * y
86+
87+
if __name__ == '__main__':
88+
calculator = Calculator()
89+
fire.Fire(calculator)
90+
```
91+
92+
#### 2.1.4 定义类和方法,使用 fire.Fire(<class>)
93+
94+
`2.1.3` 中的唯一不同点是把类而非实例对象作为 `fire.Fire` 的入参:
95+
96+
```python
97+
fire.Fire(Calculator)
98+
```
99+
100+
传递类和实例对象的基本作用是一样的,但传递类还有一个额外的特性:如果构造函数中定义了参数,那么这些参数都会作为整个命令行程序的选项参数。
101+
102+
```python
103+
import fire
104+
105+
class BrokenCalculator(object):
106+
107+
def __init__(self, offset=1):
108+
self._offset = offset
109+
110+
def add(self, x, y):
111+
return x + y + self._offset
112+
113+
def multiply(self, x, y):
114+
return x * y + self._offset
115+
116+
if __name__ == '__main__':
117+
fire.Fire(BrokenCalculator)
118+
```
119+
120+
查看帮助命令有:
121+
122+
```bash
123+
$ python example.py --help
124+
INFO: Showing help with the command 'example.py -- --help'.
125+
126+
NAME
127+
example.py
128+
129+
SYNOPSIS
130+
example.py <flags>
131+
132+
FLAGS
133+
--offset=OFFSET
134+
```
135+
136+
由此可见构造函数 `BrokenCalculator.__init__(self, offset=1)` 中的 `offset` 自动转换为了命令行中的全局选项参数 `--offset`,且默认值为 `1`
137+
138+
我们可以在命令行中这么调用:
139+
140+
```bash
141+
$ python example.py add 10 20
142+
31
143+
$ python example.py multiply 10 20
144+
201
145+
$ python example.py add 10 20 --offset=0
146+
30
147+
$ python example.py multiply 10 20 --offset=0
148+
200
149+
```
150+
151+
### 2.2 命令组/嵌套命令
152+
153+
想要实现嵌套命令,可将多个类组织起来,示例如下:
154+
155+
```python
156+
class IngestionStage(object):
157+
158+
def run(self):
159+
return 'Ingesting! Nom nom nom...'
160+
161+
class DigestionStage(object):
162+
163+
def run(self, volume=1):
164+
return ' '.join(['Burp!'] * volume)
165+
166+
def status(self):
167+
return 'Satiated.'
168+
169+
class Pipeline(object):
170+
171+
def __init__(self):
172+
self.ingestion = IngestionStage()
173+
self.digestion = DigestionStage()
174+
175+
def run(self):
176+
self.ingestion.run()
177+
self.digestion.run()
178+
179+
if __name__ == '__main__':
180+
fire.Fire(Pipeline)
181+
```
182+
183+
在上面的示例中:
184+
185+
- `IngestionStage` 实现了子命令 `run`
186+
- `DigestionStage` 实现了子命令 `run``status`
187+
- `Pipeline` 的构造函数中将 `IngestionStage` 实例化为 `ingestion`,将 `DigestionStage` 实例化为 `digestion`,就将这两个放到一个命令组中,因而支持了:
188+
- `ingestion run`
189+
- `digestion run`
190+
- `digestion status`
191+
- `Pipeline` 实现了子命令 `run`
192+
193+
因此整个命令行程序支持如下命令:
194+
195+
- `run`
196+
- `ingestion run`
197+
- `digestion run`
198+
- `digestion status`
199+
200+
然后我们就可以在命令行中这么调用:
201+
202+
```bash
203+
$ python example.py run
204+
Ingesting! Nom nom nom...
205+
Burp!
206+
$ python example.py ingestion run
207+
Ingesting! Nom nom nom...
208+
$ python example.py digestion run
209+
Burp!
210+
$ python example.py digestion status
211+
Satiated.
212+
```
213+
214+
### 2.3 属性访问
215+
216+
`属性访问``fire` 相对于其他命令行库来说一个比较独特的功能。所谓访问属性是获取预置的属性所对应的值。
217+
218+
举个例子,在命令行中指定 `--code` 来告知程序要查询的程序编码,然后希望通过 `zipcode` 属性返回邮编,通过 `city` 属性返回城市名。那么属性可实现为实例成员属性:
219+
220+
```python
221+
import fire
222+
223+
cities = {
224+
'hz': (310000, '杭州'),
225+
'bj': (100000, '北京'),
226+
}
227+
228+
229+
class City(object):
230+
231+
def __init__(self, code):
232+
info = cities.get(code)
233+
self.zipcode = info[0] if info else None
234+
self.city = info[1] if info else None
235+
236+
if __name__ == '__main__':
237+
fire.Fire(City)
238+
```
239+
240+
使用方式如下:
241+
242+
```bash
243+
$ python example.py --code bj zipcode
244+
100000
245+
$ python example.py --code hz city
246+
杭州
247+
```
248+
249+
## 三、小结
250+
251+
使用 `fire` 实现子命令和嵌套命令相对于其他命令行库来说都更加简单清晰,不仅如此,`fire` 还提供了属性访问这种较为独特的能力。
252+
253+
在下篇文章中,我们将进一步深入了解 `fire`,介绍其链式函数调用、自定义序列化、参数解析、fire 选项等更加高阶的功能。

0 commit comments

Comments
 (0)