转自:coordinate
0x00 简介
欢迎来到Python Fire指南! Python Fire是一个Python库,只需对Fire进行一次调用即可将任何Python组件转变为命令行界面。
让我们开始吧!
0x01 安装
从pypi
安装Python Fire,请运行:
pip install fire
- 1
或者,从源代码安装Python Fire,请下载源代码并运行:
python setup.py install
- 1
0x02 Hello World
Version 1: fire.Fire()
使用Fire最简单的方法是在任何Python程序结束时调用fire.Fire()
。 这会将程序的全部内容暴露给命令行。
import fire
def hello(name):
return 'Hello {name}!'.format(name=name)
if __name__ == '__main__':
fire.Fire()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
从命令行运行程序:
$ python example.py hello World
Hello World!
- 1
- 2
Version 2: fire.Fire(<fn>)
让我们稍微修改我们的程序,只将hello
函数暴露给命令行。
import fire
def hello(name):
return 'Hello {name}!'.format(name=name)
if __name__ == '__main__':
fire.Fire(hello)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
以下是我们如何从命令行运行该命令的方法:
$ python example.py World
Hello World!
- 1
- 2
注意我们不再需要指定hello
函数,因为我们调用了fire.Fire(hello)
。
Version 3: Using a main
我们也可以这样写这个程序:
import fire
def hello(name):
return 'Hello {name}!'.format(name=name)
def main():
fire.Fire(hello)
if __name__ == '__main__':
main()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
或者如果我们使用entry points
,那么代码更加简洁:
import fire
def hello(name):
return 'Hello {name}!'.format(name=name)
def main():
fire.Fire(hello)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
0x03 Exposing Multiple Commands
在前面的例子中,我们向命令行暴露了一个函数。 现在我们来看看如何将多个函数暴露给命令行。
Version 1: fire.Fire()
暴露多个命令最简单的方法是编写多个函数,然后调用Fire。
import fire
def add(x, y):
return x + y
def multiply(x, y):
return x * y
if __name__ == '__main__':
fire.Fire()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
我们可以这样使用它:
$ python example.py add 10 20
30
$ python example.py multiply 10 20
200
- 1
- 2
- 3
- 4
你会注意到Fire正确地将10
和20
解析为数字,而不是字符串。 可以看0X09 参数解析。
Version 2: fire.Fire(<dict>)
在Version 1中,我们将所有程序的功能暴露给命令行。 通过使用字典,我们可以有选择性地将一些函数暴露给命令行。
import fire
def add(x, y):
return x + y
def multiply(x, y):
return x * y
if __name__ == '__main__':
fire.Fire({
'add': add,
'multiply': multiply,
})
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
我们可以像以前一样使用它:
$ python example.py add 10 20
30
$ python example.py multiply 10 20
200
- 1
- 2
- 3
- 4
Version 3: fire.Fire(<object>)
正如在这个变体中一样,Fire 也适用于object。 这是暴露多个命令的一个好的做法。
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)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
我们可以像之前那样使用它:
$ python example.py add 10 20
30
$ python example.py multiply 10 20
200
- 1
- 2
- 3
- 4
Version 4: fire.Fire(<class>)
Fire也适用于class。 这是暴露多个命令的另一个好的做法。
import fire
class Calculator(object):
def add(self, x, y):
return x + y
def multiply(self, x, y):
return x * y
if __name__ == '__main__':
fire.Fire(Calculator)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
我们可以像之前那样使用它:
$ python example.py add 10 20
30
$ python example.py multiply 10 20
200
- 1
- 2
- 3
- 4
为什么我们更喜欢使用class而不是object? 一个原因是你可以给构造类传递参数,就像这个BrokenCalculator
例子一样。
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)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
当你使用BrokenCalculator
时,你会得到错误的答案:
$ python example.py add 10 20
31
$ python example.py multiply 10 20
201
- 1
- 2
- 3
- 4
但你可以这样解决它:
$ python example.py add 10 20 --offset=0
30
$ python example.py multiply 10 20 --offset=0
200
- 1
- 2
- 3
- 4
与调用普通函数不同,它可以通过位置参数和命名参数(--flag
语法)完成,而__init__
函数的参数必须以--flag
语法传递。 请参阅0x08 函数调用。
0x04 分组命令
下面是一个如何使用分组命令创建命令行界面的示例。
class IngestionStage(object):
def run(self):
return 'Ingesting! Nom nom nom...'
class DigestionStage(object):
def run(self, volume=1):
return ' '.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()
if __name__ == '__main__':
fire.Fire(Pipeline)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
以下是使用方式:
$ python example.py run
Ingesting! Nom nom nom...
Burp!
$ python example.py ingestion run
Ingesting! Nom nom nom...
$ python example.py digestion run
Burp!
$ python example.py digestion status
Satiated.
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
你可以用任意复杂的方式嵌套你的命令。
0x05 属性访问
到目前为止,在所有例子中,我们通过python example.py运行了示例程序的一些功能。 在下面这个例子中,我们只访问一个属性。
from airports import airports
import fire
class Airport(object):
def __init__(self, code):
self.code = code
self.name = dict(airports).get(self.code)
self.city = self.name.split(',')[0] if self.name else None
if __name__ == '__main__':
fire.Fire(Airport)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
现在我们可以使用下面的方式使用Airport
代码!
$ python example.py --code=JFK code
JFK
$ python example.py --code=SJC name
San Jose-Sunnyvale-Santa Clara, CA - Norman Y. Mineta San Jose International (SJC)
$ python example.py --code=ALB city
Albany-Schenectady-Troy
- 1
- 2
- 3
- 4
- 5
- 6
(译者注:注意这里--code
就是0x03 Exposing Multiple Commands最后说的,给__init__
传递参数,必须使用–flag语法)
0x06 链式函数调用
当您运行Fire CLI时,您可以对调用Fire的结果采取所有相同的操作。
例如,我们可以这样使用之前的Airport例子:
$ python example.py --code=ALB city upper
ALBANY-SCHENECTADY-TROY
- 1
- 2
这是可行的,因为upper是所有字符串的一种方法。
所以,如果你想设置你的函数链式调用,你只需要设置方法返回self
。下面通过一个例子说明:
import fire
class BinaryCanvas(object):
"""A canvas with which to make binary art, one bit at a time."""
def __init__(self, size=10):
self.pixels = [[0] * size for _ in range(size)]
self._size = size
self._row = 0 # The row of the cursor.
self._col = 0 # The column of the cursor.
def __str__(self):
return '\n'.join(' '.join(str(pixel) for pixel in row) for row in self.pixels)
def show(self):
print(self)
return self
def move(self, row, col):
self._row = row % self._size
self._col = col % self._size
return self
def on(self):
return self.set(1)
def off(self):
return self.set(0)
def set(self, value):
self.pixels[self._row][self._col] = value
return self
if __name__ == '__main__':
fire.Fire(BinaryCanvas)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
现在我们可以绘制东西:)。
$ python example.py move 3 3 on move 3 6 on move 6 3 on move 6 6 on move 7 4 on move 7 5 on __str__
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 1 0 0 1 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 1 0 0 1 0 0 0
0 0 0 0 1 1 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
它应该是一个笑脸。
0x07 有没有比Hello World更简单的例子?
有,下面这个程序比我们之前的Hello World例子更简单。
import fire
english = 'Hello World'
spanish = 'Hola Mundo'
fire.Fire()
- 1
- 2
- 3
- 4
你可以像这样使用它:
$ python example.py english
Hello World
$ python example.py spanish
Hola Mundo
- 1
- 2
- 3
- 4
0x08 函数调用
构造函数的参数可以使用--flag
语法--name = value
传递。
例如,考虑这个简单的类:
import fire
class Building(object):
def __init__(self, name, stories=1):
self.name = name
self.stories = 1
def climb_stairs(self, stairs_per_story=10):
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)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
我们可以通过这种方式实例化它:python example.py --name ="Sherrerd Hall"
其他函数的参数同样可以使用--flag
语法来传递。
要实例化一个Building
,然后运行climb_stairs
函数,以下命令都是有效的:
$ python example.py --name="Sherrerd Hall" --stories=3 climb_stairs 10
$ python example.py --name="Sherrerd Hall" climb_stairs --stairs_per_story=10
$ python example.py --name="Sherrerd Hall" climb_stairs --stairs-per-story 10
$ python example.py climb-stairs --stairs-per-story 10 --name="Sherrerd Hall"
- 1
- 2
- 3
- 4
你会注意到连字符和下划线( -
和_
)在成员名称和标志名称中是可以互换的。
你还会注意到构造函数的参数可以在其他函数的参数之后或函数之前。
你还会注意到,标志名称和其值之间的等号是可选的。
带有*varargs和** kwargs的函数
Fire支持带*varargs
或** kwargs
的函数。 这是一个例子:
import fire
def order_by_length(*items):
"""Orders items by length, breaking ties alphabetically."""
sorted_items = sorted(items, key=lambda item: (len(str(item)), str(item)))
return ' '.join(sorted_items)
if __name__ == '__main__':
fire.Fire(order_by_length)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
要使用它,我们这样运行:
$ python example.py dog cat elephant
cat dog elephant
- 1
- 2
你可以使用分隔符给函数提供参数。分隔符后的所有参数将用于处理函数的结果,而不是传递给函数本身。 默认分隔符是连字符 -
。
以下是我们使用分隔符的示例。
$ python example.py dog cat elephant - upper
CAT DOG ELEPHANT
- 1
- 2
如果没有分隔符,upper就会被当作另一个参数。
$ python example.py dog cat elephant upper
cat dog upper elephant
- 1
- 2
你可以使用--separator
标志更改分隔符。通过--
将标志与你的Fire指令隔开 。 以下是我们更改分隔符的示例。
$ python example.py dog cat elephant X upper -- --separator=X
CAT DOG ELEPHANT
- 1
- 2
当函数接受*varargs
,**kwargs
或你不想指定的默认值时,分隔符会很有用。作为参数,更改分隔符也很重要。
0x09 参数解析
参数的类型取决于它们的值,而不是使用它们的函数签名。 您可以从命令行传递任何Python文本:数字,字符串,元组,列表,字典(仅在某些版本的Python中支持集合)。只要它们只包含文字,您也可以任意嵌套这些集合。
为了演示这个,我们将制作一个小程序,通过这个小程序告诉我们传给它的参数类型:
import fire
fire.Fire(lambda obj: type(obj).__name__)
- 1
- 2
我们会像这样使用它:
$ 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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
在最后一个例子中,你会注意到裸词会自动替换为字符串。
要注意! 如果你想传递字符串"10"
而不是int 10
,你需要转义或者引用你的引号。 否则,Bash将会把你的引用取消并将一个没有引号的10传递给你的Python程序,在那里Fire将把它解释为一个数字。
$ python example.py 10
int
$ python example.py "10"
int
$ python example.py '"10"'
str
$ python example.py "'10'"
str
$ python example.py \"10\"
str
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
要注意! 记住Bash首先处理你的参数,然后Fire解析结果。 如果你想将dict {"name":"David Bieber"}
传递给你的程序,你可以试试这个:
$ python example.py '{"name": "David Bieber"}' # Good! Do this.
dict
$ python example.py {"name":'"David Bieber"'} # Okay.
dict
$ python example.py {"name":"David Bieber"} # Wrong. This is parsed as a string.
str
$ python example.py {"name": "David Bieber"} # Wrong. This isn't even treated as a single argument.
<error>
$ python example.py '{"name": "Justin Bieber"}' # Wrong. This is not the Bieber you're looking for. (The syntax is fine though :))
dict
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
bool类型参数
True
和False
被解析为布尔值。
你也可以通过--flag
语法--name
和--noname
来指定布尔值,它们分别将名称设置为True
和False
。
继续前面的例子,我们可以运行以下任何一个:
$ python example.py --obj=True
bool
$ python example.py --obj=False
bool
$ python example.py --obj
bool
$ python example.py --noobj
bool
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
要注意! 如果除另一个标志之外的标志紧跟在应该是布尔值的标志之后,该标志将取代该标志的值而不是布尔值。 您可以解决这个问题:通过在最后一个标志之后放置一个分隔符,明确指出布尔标志的值(如--obj = True
),或者确保在布尔标志参数后面有另一个标志。
0x10 Using Fire Flags
Fire CLI自己也带有一些标志。这些标志应该通过--
与Fire命令分开。如果至少有一个--
,那么在最后一个--
之后的参数将被视为标志,而在最后一个--
之前的所有参数都被视为Fire命令的一部分。
一个有用的标志是--interactive
。在任何CLI上使用--interactive
标志输入一个Python REPL,其中包含已调用Fire的上下文中使用的所有模块和变量。其他有用的变量,例如Fire命令的结果也将可用。像这样使用这个特性:python example.py -- --interactive
。
你可以将帮助标志添加到任何命令中,用来查看帮助和使用信息。 Fire将文档结合到它生成的帮助和使用信息中。即使你省略了--
,Fire也会试图提供帮助,它会将标志从Fire命令中分离出来。但并不总是如此,因为help是一个有效的参数名称。像这样使用这个特性:python example.py -- --help
。
下面Reference部分中显示了完整的可用标志集。
Reference
Setup | Command | Notes |
---|---|---|
install | pip install fire |
Creating a CLI
Creating a CLI | Command | Notes |
---|---|---|
import | import fire | |
Call | fire.Fire() | Turns the current module into a Fire CLI. |
Call | fire.Fire(component) | Turns component into a Fire CLI. |
Flags
Using a CLI | Command | Notes |
---|---|---|
Help | command -- --help | Show help and usage information for the command. |
REPL | command -- --interactive | Enter interactive mode. |
Separator | command -- --separator=X | This sets the separator to X . The default separator is - . |
Completion | command -- --completion | Generate a completion script for the CLI. |
Trace | command -- --trace | Gets a Fire trace for the command. |
Verbose | command -- --verbose | Include private members in the output. |
Note that flags are separated from the Fire command by an isolated – arg.
https://github.com/google/python-fire/blob/master/docs/guide.md
<link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/markdown_views-ea0013b516.css">
</div>