|
| 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