Fire介绍
fire可以根据任何 Python 对象自动生成命令行接口。它有如下特性:
- 能以简单的方式生成 CLI
- 是一个开发和调试 Python 代码的实用工具
- 能将现存代码或别人的代码转换为 CLI
- 使得在 Bash 和 Python 间的转换变得更容易
- 通过预先为 REPL 设置所需的模块和变量,使得实用 REPL 更加容易
安装
pip install fire
调用方法
函数调用
import fire
def hello(name="World"):
return 'Hello {name}!'.format(name=name)
if __name__ == '__main__':
fire.Fire(hello)
在上述例子中定义一个 hello
函数,它接受 name
参数,并且有默认值 "World"。使用 fire.Fire(hello)
即可非常简单快速地实现命令功能,这个命令行就接受 --name
选项,不提供时使用默认值 "World",提供时就按提供的值来。
可在命令行中执行下列命令:
$ python hello.py
Hello World!
$ python hello.py --name=Xj
Hello Xj!
$ python hello.py --help
INFO: Showing help with the command 'hello.py -- --help'.
NAME
hello.py
SYNOPSIS
hello.py
FLAGS
--name=NAME
隐式使用 fire.Fire()
实现子命令最简单的方式就是定义若干个函数,每个函数名隐式就是子命令名称,然后调用 fire.Fire()
变将当前模块所有的函数解析为对应的子命令的处理函数。
import fire
def add(x, y):
return x + y
def multiply(x, y):
return x * y
if __name__ == '__main__':
fire.Fire()
然后我们就可以在命令行中这么调用。
$ python example.py add 10 20
30
$ python example.py multiply 10 20
200
显式使用 fire.Fire()
有时我们可能只想把部分函数当做子命令,或者是希望子命令名称和函数名称不一样。这个时候我们就可以通过字典对象显式地告诉 fire
。
字典对象的形式为 {'子命令名称': 函数}
,比如前面的示例中,我们希望最终的子命令为 add2
和 mul2
,那么就可以这么写:
fire.Fire({
'add2': add,
'mul2': multiply,
})
然后我们就可以在命令行中这么调用。
$ python example.py add2 10 20
30
$ python example.py mul2 10 20
200
类调用
使用函数是最简单的方式,如果我们想以更有组织的方式来实现,比如使用类,fire
也是支持的。
import fire
class Calculator(object):
"""A simple calculator class."""
def double(self, number):
return 2 * number
def triple(self, number):
return 3 * number
if __name__ == '__main__':
fire.Fire(Calculator)
在上述例子中定义一个 Calculator
类,它有两个实例方法 double
和 triple
,并且都接受 number
参数,没有默认值。使用 fire.Fire(Calculator)
即可非常简单快速地实现命令功能,这个命令行支持两个子命令 double
和 triple
,位置参数 NUMBER
或选项参数 --number
可在命令行中执行下列命令:
$ python calculator.py double 10
20
$ python calculator.py triple --number=15
45
$ python calculator.py double --help
INFO: Showing help with the command 'calculator.py double -- --help'.
NAME
calculator.py double
SYNOPSIS
calculator.py double NUMBER
POSITIONAL ARGUMENTS
NUMBER
NOTES
You can also use flags syntax for POSITIONAL ARGUMENTS
实例化的对象使用 fire.Fire()
将类实例化,并把实例化的对象作为 fire.Fire
的入参:
import fire
class Calculator(object):
def add(self, x, y):
return x + y
def multiply(self, x, y):
return x * y
if __name__ == '__main__':
calculator = Calculator()
fire.Fire(calculator)
类使用 fire.Fire()
这里把类而非实例对象作为 fire.Fire
的入参:
fire.Fire(Calculator)
传递类和实例对象的基本作用是一样的,但传递类还有一个额外的特性:如果构造函数中定义了参数,那么这些参数都会作为整个命令行程序的选项参数。
import fire
class BrokenCalculator(object):
def __init__(self, offset=1):
self._offset = offset
def add(self, x, y):
return x + y + self._offset
def multiply(self, x, y):
return x * y + self._offset
if __name__ == '__main__':
fire.Fire(BrokenCalculator)
查看帮助命令有:
$ python example.py --help
INFO: Showing help with the command 'example.py -- --help'.
NAME
example.py
SYNOPSIS
example.py
FLAGS
--offset=OFFSET
由此可见构造函数 BrokenCalculator.__init__(self, offset=1)
中的 offset
自动转换为了命令行中的全局选项参数 --offset
,且默认值为 1
。
我们可以在命令行中这么调用:
$ python example.py add 10 20
31
$ python example.py multiply 10 20
201
$ python example.py add 10 20 --offset=0
30
$ python example.py multiply 10 20 --offset=0
200
命令组/嵌套命令
想要实现嵌套命令,可将多个类组织起来,示例如下:
import fire
class IngestionStage(object):
def run(self):
print('Ingesting! Nom nom nom...')
class DigestionStage(object):
def run(self, volume=1):
print(' '.join(['Burp!'] * volume))
def status(self):
return 'Satiated.'
class Pipeline(object):
def __init__(self):
self.ingestion = IngestionStage()
self.digestion = DigestionStage()
def run(self):
self.ingestion.run()
self.digestion.run()
print('pipeline is runing.')
if __name__ == '__main__':
fire.Fire(Pipeline)
在上面的示例中:
-
IngestionStage
实现了子命令run
-
DigestionStage
实现了子命令run
和status
-
Pipeline
的构造函数中将IngestionStage
实例化为ingestion
,将DigestionStage
实例化为digestion
,就将这两个放到一个命令组中,因而支持了: ingestion run
digestion run
digestion status
-
Pipeline
实现了子命令run
因此整个命令行程序支持如下命令:
run
ingestion run
digestion run
digestion status
然后我们就可以在命令行中这么调用:
$ python example.py run
Ingesting! Nom nom nom...
Burp!
pipeline is runing.
$ python example.py ingestion run
Ingesting! Nom nom nom...
$ python example.py digestion run
Burp!
$ python example.py digestion status
Satiated.
属性访问
属性访问
是 fire
相对于其他命令行库来说一个比较独特的功能。所谓访问属性是获取预置的属性所对应的值。
举个例子,在命令行中指定 --code
来告知程序要查询的程序编码,然后希望通过 zipcode
属性返回邮编,通过 city
属性返回城市名。那么属性可实现为实例成员属性:
import fire
cities = {
'hz': (310000, '杭州'),
'bj': (100000, '北京'),
}
class City(object):
def __init__(self, code):
info = cities.get(code)
self.zipcode = info[0] if info else None
self.city = info[1] if info else None
if __name__ == '__main__':
fire.Fire(City)
使用方式如下:
$ python example.py --code bj zipcode
100000
$ python example.py --code hz city
杭州
链式调用
在 Fire CLI
中,你可以通过链式调用不断地对上一个结果进行处理。
想做到这一点也很简单,就是在实例方法中返回 self
即可。
在下面的示例中,我们实现了一个简单的四则运算命令,可链式调用 add
、sub
、mul
和 div
。
import fire
class Calculator:
def __init__(self):
self.result = 0
self.express = '0'
def __str__(self):
return f'{self.express} = {self.result}'
def add(self, x):
self.result += x
self.express = f'{self.express}+{x}'
return self
def sub(self, x):
self.result -= x
self.express = f'{self.express}-{x}'
return self
def mul(self, x):
self.result *= x
self.express = f'({self.express})*{x}'
return self
def div(self, x):
self.result /= x
self.express = f'({self.express})/{x}'
return self
if __name__ == '__main__':
fire.Fire(Calculator)
上述代码中的 add
、sub
、mul
、div
分别对应加、减、乘、除的逻辑,每个方法都接受 x
参数作为参与运算的数字,返回值均为 self
,这样就可以无限次地链式调用。在命令行中链式调用结束后,会最终调用到 __str__
方法将结果打印出来。
其中,__str__
在 fire
中用来完成自定义序列化。如果不提供这个方法,在链式调用完成后将会打印帮助内容。
比如,我们可以这么调用:
$ python calculator.py add 1 sub 2 mul 3 div 4
((+1-2)*3)/4 = -0.75
$ python calculator.py add 1 sub 2 mul 3 div 4 add 4 sub 3 mul 2 div 1
((((0+1-2)*3)/4+4-3)*2)/1 = 0.5
参数
位置参数和选项参数
import fire
class Building(object):
def __init__(self, name, stories=1):
self.name = name
self.stories = stories
def __str__(self):
return f'name: {self.name}, stories: {self.stories}'
def climb_stairs(self, stairs_per_story=10):
yield self.name
for story in range(self.stories):
for stair in range(1, stairs_per_story):
yield stair
yield 'Phew!'
yield 'Done!'
if __name__ == '__main__':
fire.Fire(Building)
构造函数中定义的参数(如 name
和 stories
)在命令行中仅为选项参数(如 --name
和 --stories
)。我们可以这么调用:
$ python example.py --name="Sherrerd Hall" --stories=3
name: Sherrerd Hall, stories: 3
构造函数中定义的参数可在命令中放于任意位置。比如下面两个调用都是可以的。
$ python example.py --name="Sherrerd Hall" climb-stairs --stairs-per-story 10
Sherrerd Hall
1
2
3
4
5
6
7
8
9
Phew!
Done!
$ python example.py climb-stairs --stairs-per-story 10 --name="Sherrerd Hall"
Sherrerd Hall
1
2
3
4
5
6
7
8
9
Phew!
Done!
构造函数和普通方法中定义的默认参数(如 stories
),在命令行中是可选的。我们可以这么调用
$ python example.py --name="Sherrerd Hall"
普通方法中定义的参数(如 stairs_per_story
)在命令行中即可以是位置参数,也可以是选项参数。我们可以这么调用:
# 作为位置参数
$ python example.py --name="Sherrerd Hall" climb_stairs 10
# 作为选项参数
$ python example.py --name="Sherrerd Hall" climb_stairs --stairs_per_story=10
此外,fire
还支持在函数中定义 *args
和 **kwargs
。
import fire
def fargs(*args):
return str(args)
def fkwargs(**kwargs):
return str(kwargs)
if __name__ == '__main__':
fire.Fire()
函数中的 *args
在命令行中为位置参数。我们可以这么调用
$ python example.py fargs a b c
('a', 'b', 'c')
函数中的 **kwargs
在命令行中为选项参数。我们可以这么调用
python example.py fkwargs --a a1 --b b2 --c c3
{'a': 'a1', 'b': 'b2', 'c': 'c3'}
通过分隔符 -
可显式告知分隔符后的为子命令,而非命令的参数。且看下面的示例:
# 没有使用分隔符,upper 被作为位置参数
$ python example.py fargs a b c upper
('a', 'b', 'c', 'upper')
# 使用了分隔符,upper 被作为子命令
$ python example.py fargs a b c - upper
('A', 'B', 'C')
通过 fire
内置的 --separator
可以自定义分隔符,此选项参数需要跟在单独的 --
后面
$ python example.py fargs a b c X upper -- --separator=X
('A', 'B', 'C')
参数类型
在 fire
中,参数的类型由其值决定,通过下面的简单代码,我们可以看到给不同的值时,fire
会解析为什么类型.
import fire
fire.Fire(lambda obj: type(obj).__name__)
$ python example.py 10
int
$ python example.py 10.0
float
$ python example.py hello
str
$ python example.py '(1,2)'
tuple
$ python example.py [1,2]
list
$ python example.py True
bool
$ python example.py {name: David}
dict
如果想传递字符串形式的数字,那就需要小心引号了,要么把引号引起来,要么转义引号
# 数字 10
$ python example.py 10
int
# 没有对引号处理,仍然是数字10
$ python example.py "10"
int
# 把引号引起来,所以是字符串“10”
$ python example.py '"10"'
str
# 另一种把引号引起来的形式
$ python example.py "'10'"
str
# 转义引号
$ python example.py \"10\"
str
考虑下更复杂的场景,如果传递的是字典,在字典中有字符串,那么也是要小心引号的
# 推荐做法
$ python example.py '{"name": "David Bieber"}'
dict
# 也是可以的
$ python example.py {"name":'"David Bieber"'}
dict
# 错误,会被解析为字符串
$ python example.py {"name":"David Bieber"}
str
# 错误,不会作为单个参数(因为中间有空格),报错
$ python example.py {"name": "David Bieber"}
如果值为 True
或 False
将为视为布尔值,fire
还支持通过 --name
将 name
设为 True
,或通过 --noname
将 name
设为 False
$ python example.py --obj=True
bool
$ python example.py --obj=False
bool
$ python example.py --obj
bool
$ python example.py --noobj
bool
Fire 内置选项参数
Fire 内置了一些选项参数,以帮助我们更容易地使用命令行程序。若想使用内置的选项功能,需要将选项参数跟在 --
后,在上文中,我们介绍了 --separator
参数,除了它,fire
还支持以下选项参数:
-
command -- --help
列出详细的帮助信息 -
command -- --interactive
进入交互式模式 -
command -- --completion [shell]
生成 CLI 程序的自动补全脚本,以支持自动补全 -
command -- --trace
获取命令的 Fire 追踪以了解调用 Fire 后究竟发生了什么 -
command -- --verbose
获取包含私有成员在内的详情