0x00 简介

欢迎来到Python Fire指南! Python Fire是一个Python库,只需对Fire进行一次调用即可将任何Python组件转变为命令行界面。

让我们开始吧!

0x01 安装

pypi安装Python Fire,请运行:

pip install fire

或者,从源代码安装Python Fire,请下载源代码并运行:

python setup.py install

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

从命令行运行程序:

$ python example.py hello World
Hello World!
Version 2: fire.Fire()

让我们稍微修改我们的程序,只将hello函数暴露给命令行。

import fire

def hello(name):
  return 'Hello {name}!'.format(name=name)

if __name__ == '__main__':
  fire.Fire(hello)

以下是我们如何从命令行运行该命令的方法:

$ python example.py World
Hello World!

注意我们不再需要指定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()

或者如果我们使用entry points,那么代码更加简洁:

import fire

def hello(name):
  return 'Hello {name}!'.format(name=name)

def main():
  fire.Fire(hello)

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

我们可以这样使用它:

$ python example.py add 10 20
30
$ python example.py multiply 10 20
200

你会注意到Fire正确地将1020解析为数字,而不是字符串。 可以看0X09 参数解析

Version 2: fire.Fire()

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,
  })

我们可以像以前一样使用它:

$ python example.py add 10 20
30
$ python example.py multiply 10 20
200
Version 3: fire.Fire()

正如在这个变体中一样,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)

我们可以像之前那样使用它:

$ python example.py add 10 20
30
$ python example.py multiply 10 20
200
Version 4: fire.Fire()

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)

我们可以像之前那样使用它:

$ python example.py add 10 20
30
$ python example.py multiply 10 20
200

为什么我们更喜欢使用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)

当你使用BrokenCalculator时,你会得到错误的答案:

$ 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

与调用普通函数不同,它可以通过位置参数和命名参数(--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)

以下是使用方式:

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

你可以用任意复杂的方式嵌套你的命令。

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)

现在我们可以使用下面的方式使用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

(译者注:注意这里--code就是0x03 Exposing Multiple Commands最后说的,给__init__传递参数,必须使用–flag语法)

0x06 链式函数调用

当您运行Fire CLI时,您可以对调用Fire的结果采取所有相同的操作。

例如,我们可以这样使用之前的Airport例子:

$ python example.py --code=ALB city upper
ALBANY-SCHENECTADY-TROY

这是可行的,因为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)

现在我们可以绘制东西:)。

$ 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

它应该是一个笑脸。

0x07 有没有比Hello World更简单的例子?

有,下面这个程序比我们之前的Hello World例子更简单。

import fire
english = 'Hello World'
spanish = 'Hola Mundo'
fire.Fire()

你可以像这样使用它:

$ python example.py english
Hello World
$ python example.py spanish
Hola Mundo

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)

我们可以通过这种方式实例化它: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"

你会注意到连字符和下划线( -_)在成员名称和标志名称中是可以互换的。

你还会注意到构造函数的参数可以在其他函数的参数之后或函数之前。

你还会注意到,标志名称和其值之间的等号是可选的。

带有*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)

要使用它,我们这样运行:

$ python example.py dog cat elephant
cat dog elephant

你可以使用分隔符给函数提供参数。分隔符后的所有参数将用于处理函数的结果,而不是传递给函数本身。 默认分隔符是连字符 -

以下是我们使用分隔符的示例。

$ python example.py dog cat elephant - upper
CAT DOG ELEPHANT

如果没有分隔符,upper就会被当作另一个参数。

$ python example.py dog cat elephant upper
cat dog upper elephant

你可以使用--separator标志更改分隔符。通过--将标志与你的Fire指令隔开 。 以下是我们更改分隔符的示例。

$ python example.py dog cat elephant X upper -- --separator=X
CAT DOG ELEPHANT

当函数接受*varargs**kwargs或你不想指定的默认值时,分隔符会很有用。作为参数,更改分隔符也很重要。

0x09 参数解析

参数的类型取决于它们的值,而不是使用它们的函数签名。 您可以从命令行传递任何Python文本:数字,字符串,元组,列表,字典(仅在某些版本的Python中支持集合)。只要它们只包含文字,您也可以任意嵌套这些集合。

为了演示这个,我们将制作一个小程序,通过这个小程序告诉我们传给它的参数类型:

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"而不是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

要注意! 记住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
bool类型参数

TrueFalse被解析为布尔值。

你也可以通过--flag语法--name--noname来指定布尔值,它们分别将名称设置为TrueFalse

继续前面的例子,我们可以运行以下任何一个:

$ python example.py --obj=True
bool
$ python example.py --obj=False
bool
$ python example.py --obj
bool
$ python example.py --noobj
bool

要注意! 如果除另一个标志之外的标志紧跟在应该是布尔值的标志之后,该标志将取代该标志的值而不是布尔值。 您可以解决这个问题:通过在最后一个标志之后放置一个分隔符,明确指出布尔标志的值(如--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