Fire命令行接口工具


主要参考和对官方文档进行二次浓缩而成

虽然有个 click 库来完成该任务,但是发现 fire 更好用!

Fire命令行接口工具


1. 引入原因

讲述为什么需要使用 fire 这个第三方库呢?

  • 当我需要写一个或多个命令行脚本工具的时候,头脑中首先会想到使用 Python 标准库中自带的 argparse 模块来完成该任务。但是写完之后,会发现 argparse 又臭又长,很不美观。
import argparse
import requests

def scrape(url, timeout=10):
    response = requests.get(url, timeout=timeout)
    print(response.text)

parser = argparse.ArgumentParser(description='Web Scrape')
parser.add_argument('url', type=str, help='scrapt url address')
parser.add_argument('timeout', type=int, help='run wait timeout')

if __name__ == '__main__':
    args = parser.parse_args()
    scrape(args.url, args.timeout)
  • 此时,此时忽然想到使用 pallets/click 这个第三方库,毕竟之前还做过 笔记 不是。起初使用的话,没有多大问题,但是程序复杂了之后,管理起来还是挺麻烦的,尤其是需要对类进行操作的时候,就更为难受了。
import click
import requests

@click.command()
@click.argument('url', type=click.STRING)
@click.argument('timeout', type=click.INT)
def scrape(url, timeout=10):
    response = requests.get(url, timeout=timeout)
    print(response.text)

if __name__ == '__main__':
    scrape()
  • 此时,就需要使用我们这里介绍的 fire 第三方库了。当使用了了之后,会发现其命令行变得非常简单,只需要两行代码就可以完成了。
import fire
import requests

def scrape(url, timeout=10):
    response = requests.get(url, timeout=timeout)
    print(response.text)

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

2. 简单介绍

Fire 是一个用于自动生成命令行接口(CLIs)的库!

如果你仔细看官方文档的话,在特性这里,其实就想说一句话,那就是:如果你需要写命令行工具,那用我就对了!同时,从仓库地址可以看到是 Google 开源的工具,Start 数量已经是 Click 的两倍还要多了。

  • 安装方式
# pip
$ pip3 install fire

# conda
$ conda install fire -c conda-forge

# source
$ python3 setup.py install
  • 相关概念 - 创建命令行
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.
  • 相关概念 - 使用命令行
Using a CLI Command Notes
Help command --help or command -- --help
REPL command -- --interactive Enters interactive mode.
Separator command -- --separator=X Sets separator to X. Default separator is -.
Completion command -- --completion [shell] Generates a completion script for the CLI.
Trace command -- --trace Gets a Fire trace for the command.
Verbose command -- --verbose

3. 简单使用

介绍该工具的几种简单的使用方式!

  • [1] 暴露一个命令 - 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!
  • [2] 暴露一个命令 - fire.Fire(<fn>) - 绑定指定对象
import fire

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

if __name__ == '__main__':
    fire.Fire(hello)
# 绑定指定对象
$ python example.py World
Hello World!
  • [3] 暴露一个命令 - Using a main - 间接调用
import fire

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

def main():
    fire.Fire(hello)

if __name__ == '__main__':
    main()
  • [4] 暴露一个命令 - Fire Without Code Changes - 定义即可使用
def hello(name):
    return 'Hello {name}!'.format(name=name)
# 定义即可使用
$ python -m fire example hello --name=World
Hello World!

# 定义即可使用
$ python -m fire example.py hello --name=World
Hello World!

  • [1] 暴露多个命令 - 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
  • [2] 暴露多个命令 - fire.Fire(dict) - 子命令重命名
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
  • [3] 暴露多个命令 - 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
  • [4] 暴露多个命令 - fire.Fire(class) - 对类进行使用
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 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

4. 进阶特性

介绍该工具的进阶一点的特性!

  • [1] 命令分组 - 多个类分组合并
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()
        return 'Pipeline complete'

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.
  • [2] 访问属性 - 只是访问一个属性
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)
$ 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
  • [3] 改变函数回调 - 对回传的数据进行二次操作
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 --code=ALB city upper
ALBANY-SCHENECTADY-TROY
$ 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
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
  • [4] 更简单的调用方式 - 真的简单
import fire

english = 'Hello World'
spanish = 'Hola Mundo'
fire.Fire()
$ python example.py english
Hello World

$ python example.py spanish
Hola Mundo
  • [5] 直接调用 call 方法 - 通过 –name=value 传值
import fire

class Building(object):
    def __init__(self, name, stories=1):
        self.name = name
        self.stories = stories

    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"
$ 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"
  • [6] 对于泛解析字典和数组的使用
import fire

def order_by_length(*items):
    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

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

$ python example.py dog cat elephant X upper -- --separator=X
CAT DOG ELEPHANT
  • [7] 参数传递 - 实参的类型由它们的值决定
import fire

fire.Fire(lambda obj: type(obj).__name__)
$ python example.py 10
int

$ python example.py "10"
int

$ python example.py '"10"'
str

$ 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

$ python example.py --obj=True
bool

$ python example.py --obj=False
bool

文章作者: Escape
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Escape !
  目录