python制作命令行工具——fire

一、快速介绍

来一波官方介绍。

  • Python Fire是一个库,用于从任何Python对象自动生成命令行接口。
  • 是用python创建CLI的一种简单方法。
  • 是开发和调试Python代码的一个有用工具。
  • Python Fire帮助探索现有代码或将其他人的代码转换为CLI。
  • 使得Bash和Python之间的转换更加容易。
  • 通过使用已经导入和创建的模块和变量来设置REPL, Python Fire使使用Python REPL变得更容易。

没听懂 ???

不是太明白 ???

不要紧,看完本篇就懂了。

二、快速安装

pip install fire
conda install fire -c conda-forge
1. git clone https://github.com/google/python-fire.git
2. cd python-fire
3. python setup.py install

Github地址: python-fire

三、快速上手

实践出真知

创建一个test.py文件,写入以下内容

import fire

def test(your_var="default_value"):
	return 'This is a test ! value : %s' % your_var

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

咱们来看一下效果

# 缺省参数
root@node:~# python test.py 
This is a test ! value : default_value

# 关键字参数
root@node:~# python test.py --your_var=newValue
This is a test ! value : newValue

# 位置参数
root@node:~# python test.py localtionValue
This is a test ! value : localtionValue

现在呢,我们反过头来看一下官方介绍的第一行:

Python Fire是一个库,用于从任何Python对象自动生成命令行接口。

注意关键字:任何python对象。这意味着什么?

我们来看一段代码:

import fire

boy_name = 'XiaoMing'
girl_name = 'XiaoHong'

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

试一下: python test.py boy_name

是不是明白了些什么。

聊完这 缺省参数关键字参数位置参数 ,当然不能少了 * args 和 ** kwargs .

还是来看代码示例:

import fire

def run(*args):
    arg_list = list(args)
    return ' | '.join(arg_list)

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

跑一下就懂啦

root@node:~# python test.py run qwe rty uio asd fgh
qwe | rty | uio | asd | fgh

官方给的示例是这个样子的~~~

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

除此之外呢,我们还可以给输出结果加点料,还是刚才我们写的那个例子:

root@node:~# python test.py run qwe rty uio asd fgh - upper
QWE | RTY | UIO | ASD | FGH

在这里,我们通过命令行对传入的对象和调用结果执行相同的操作,譬如这里的 upper

敲黑板划重点: 分隔符 “ - ” 之后的所有参数都将用于处理函数的结果,而不是传递给函数本身。默认的分隔符是连字符 “ - ”。

默认的分隔符也是可以改的,用到了 fire 的内置参数。

root@node:~# python test.py run qwe rty uio asd fgh X upper -- --separator=X
QWE | RTY | UIO | ASD | FGH

其中的 separator 就是fire的一个内置参数,更多内置参数文末有提到。

我们再来看一下 fire 给我们提供的命令行传参时, 数据的类型 。比较特殊的是, fire 根据值决定类型。

import fire
fire.Fire(lambda obj: type(obj).__name__)

如果有刚学python的小伙伴,记得一定要学一下 lambda 函数,在这里我可以转化为普通写法。

import fire

def test(obj):
    return type(obj).__name__

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

通过简单的一行代码来看一下各种数据类型如何通过命令行传参:

$ 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

但是当你想传递一个str类型的10,你就要注意了,看以下例子:

$ 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

我们可以看到,你虽然敲了 "10" ,但是依然被判定为 int ,bash会自动处理掉你参数的第一层引号。所以,如果想传 str 类型的10,要再加一层引号,单双引号分开用,或者把引号转义。

如果要传的是dict参数,那就更要小心谨慎了。

# 标准写法
$ 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"}  # Wrong. This isn't even treated as a single argument.
<error>

到这里,我想大家应该大概明白了 fire 的方便快捷之处。

到了这一步的时候,虽然实现了基本功能,但还是和平时我们使用的 linux 命令行工具有很大的区别:

  1. 每次跑命令都要再敲一个python

  2. 每次还要指向指定的py文件或到指定的目录下

    首先说第一个问题,每次多敲六个字母和一个空格,作为一个linux命令行工具是非常不合格的,本来命令行工具就在追求简单化,这种指定解释器的操作我们当然要尽可能省掉咯

    第二个问题,老是指定文件的目录就更麻烦了,日常使用的时候在不同的服务器跑命令还要想想放在哪里,而且如果使用绝对路径的话,更会导致命令的冗长。

下面我们来解决一下这两个“小”问题:

  1. 在文件的第一行指定python解释器,这样就无需在我们运行该文件时再指定解释器
#!/usr/bin/python

import fire

def test(your_var="default_value"):
	return 'This is a test ! value : %s' % your_var

if __name__ == '__main__':
    fire.Fire(test)
  1. 增加文件的可执行权限
root@node:~# chmod +x test.py
  1. 美化以下,去掉小尾巴(仅仅是给文件改了个名字, 这一步非必须)
root@node:~# mv test.py mytool
  1. 做个软连接,可以随时随地找得到该命令
root@node:~# ln -s /root/mytool /usr/bin/mytool

附:如果需要指定编码的话,可以在文件头部加一行,比如

#!/usr/bin/python
# coding: utf-8

这个时候,我们随意在服务器的任意位置执行

root@node:~# mytool
This is a test ! value : default_value

root@node:~# mytool --your_var=newValue
This is a test ! value : newValue

root@node:~# mytool localtionValue
This is a test ! value : localtionValue

Perfection !

如果你已经走到这一步的话,其实已经能写很多简单的命令行工具了。

为什么说简单呢,目前都是使用函数来完成一个个命令的逻辑,多一个子命令多写一个函数,慢慢的就会让这个文件变的庞杂和冗余。而且久而久之,肯定会出现一些看起来很相似,却要使用ctrl + c-v大法去完成的事情。甚至有一些逻辑,想要实现还要自己去做更复杂的逻辑。

四、快速进阶

此时,一年级的已经可以下课了,二年级的请注意听讲了,下面,我们要讲的是:

类的使用

命令嵌套

属性访问

链式调用

4.1 类的使用

通过一个简单的算数类来了解其用法,在下列用法中,我们在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)

以下是调用测试

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

当然我们也可以注册一个类。

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

就这?当然不会,我们还可以通过参数控制实例属性,就像下面的例子:

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)

我们可以看到,新增了一个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

4.2 命令嵌套

通过不同的类来控制某些同名命令,其实也是将各个命令分门别类更具条理性的管理。可以看到以下用法。

import fire

class Sing:
    def run(self):
        print('sing sing sing ...')

class Dance:
    def run(self):
        print('dance dance dance ...')

    def status(self):
        print('Around.')


class Pipeline:
    def __init__(self):
        self.sing = Sing()
        self.dance = Dance()

    def run(self):
        self.sing.run()
        self.dance.run()
        self.dance.status()

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

跑跑看:

# python3 ball.py run
sing sing sing ...
dance dance dance ...
Around.
# python3 ball.py sing run
sing sing sing ...
# python3 ball.py dance run
dance dance dance ...
# python3 ball.py dance status
Around.

根据自定义的一个Pipeline类,我们可以自己组合想要的命令行效果,给子命令再分配不同的子集。

4.3 属性访问

其实前面说到类的时候已经简单的说过属性访问(就是那个offset的例子,行啦,忘了就不用往上翻了),这里再详细举例说明一下。

# python3 test.py --age=6 outinfo
Xiao Ming is 6 years old and in the First grade

# python3 test.py --age=7 outinfo
Xiao Ming is 7 years old and in the Second grade

# python3 test.py --age=8 outinfo
Xiao Ming is 8 years old and in the Third grade

综上,我们可以通过控制类的属性来构造类对象。

唠到这儿了,再唠一个骚操作

4.4 链式调用

官方给的例子不太好看,没有那么让人一眼就看懂的感觉,找了个四则运算的简单示例:

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)

函数名呢, addsubmuldiv 分别对应 加、减、乘、除四则运算,每个方法都接受 x 参数去运算,返回 self ,这样不论往后链式调用多少次都可以,结束调用到 __str__ 打印出结果。

__str__fire 中用来完成自定义序列化。如果不提供这个方法,在链式调用完成后将会打印帮助内容。

# python3 test.py add 2 sub 1.5 mul 3 div 2
((0+2-1.5)*3)/2 = 0.75

# python3 test.py add 4 sub 2.5 mul 2 div 4 mul 3 sub 5 add 2
(((0+4-2.5)*2)/4)*3-5+2 = -0.75

看完这个大家应该明白链式调用的运用了,这个时候再来看一下官方示例也许会轻松一些。

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

PS:我要不说,谁能看出来这是个笑脸???

最后一课

最后看看官方给出的 fire 的内置参数吧,具体怎么应用大家就自己研究咯。

Flags

Using a CLICommandNotes
Helpcommand -- --help显示命令的帮助和使用信息。
REPLcommand -- --interactive进入交互模式。
Separatorcommand -- --separator=X这将分隔符设置为' X '。默认分隔符是“-”。
Completioncommand -- --completion [shell]为CLI生成一个补全的shell脚本。
Tracecommand -- --trace跟踪fire命令调用后发生了啥子。
Verbosecommand -- --verbose在输出中包含私有成员。

fire-GitHub地址

本文的文字及图片来源于网络,仅供学习、交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理

想要获取更多Python学习资料可以加
QQ:2955637827私聊
或加Q群630390733
大家一起来学习讨论吧!

 

热门文章

暂无图片
编程学习 ·

论怂

从心为怂&#xff0c;世人的误解 老祖宗还是有智慧的&#xff0c;这不是个肯定句。这是个判断语句&#xff0c;if you want to follow your heart,you should be 怂
暂无图片
编程学习 ·

面试总结

面试总结 1.面试以项目为开始展开&#xff0c;不断深入&#xff0c;从项目的背景一直介绍到项目用到的技术原理。会不断的问为什么 2.考察底层原理 3.语言表达能力很重要&#xff0c;需要把原理背景说清楚 下面是题目&#xff0c;欢迎大家把想到的答案写在评论区一起讨论 题目&…
暂无图片
编程学习 ·

Windows安装redis

Windows安装redis 可以到https://github.com/microsoftarchive/redis/releases下载最新的windows版本 双击 redis-server 就会安装默认配置启动redis服务 这样就说明在windows下启动redis成功了&#xff0c;端口号是6379
暂无图片
编程学习 ·

Victor CMS 未授权sql注入(CVE-2020-29280)漏洞复现

0X00简介 The Victor CMS v1.0版本存在安全漏洞&#xff0c;该漏洞源于通过search.php页面上的“search”参数造成的。 0X01影响范围 The Victor CMS v1.0 0X02漏洞复现 在search.php中search参数未经过滤就直接和sql语句拼接导致sql注入漏洞 1.访问首页点击搜索框&#x…
暂无图片
编程学习 ·

从根本上把握防护DDoS的核心要素,不怕DDoS攻击防不住

随着互联网技术的广泛应用和飞速发展&#xff0c;DDoS流量攻击案件频发&#xff0c;网站防护DDoS变得越来越重要&#xff0c;只有采取有效的网站安全防护措施&#xff0c;才能够更好的防御黑客的攻击。 近日&#xff0c;公安部的微信公众号发布了一则重要提醒&#xff1a;你常…
暂无图片
编程学习 ·

211本,字节视频1面凉凉,三天后,却收到了美团offer?

写在开头 2020年的开端&#xff0c;似乎并不那么幸运&#xff0c;新冠肺炎的出现&#xff0c;对我们的生活、工作和学习都造成了非常大的影响&#xff0c;很多公司延期返工了&#xff0c;原本的金三银四似乎也不会如往年那般热闹&#xff0c;但这并不意味着我们就什么都不去做…
暂无图片
编程学习 ·

12.13补课周

记录2leetcode 104 递归 非递归的栈的还不会写 思路 利用DFS,深度递归&#xff1b;总深度为左石子树最大深度加1; 空树深度为0&#xff61; 代码 // An highlighted block /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode l…
暂无图片
编程学习 ·

程序猿小白艰辛学习的第三天

变量的三大组成部分 变量名&#xff1a;用来找值 赋值符号&#xff1a;将变量值的内存地址绑定给变量名 变量值&#xff1a;记录的事物的状态&#xff0c;也就是我们存储的数据 引用 print&#xff08;变量名&#xff09;运行会显示出变量值 变量名 变量名的命名应该遵循见名…
暂无图片
编程学习 ·

2020-12-17

显示如下问题&#xff1a;、Android studio No Debuggable Processes 解决方法&#xff1a;选中 Run->Debug 重新Debug编译运行就可以了。
暂无图片
编程学习 ·

大数据与云计算

大数据与云计算什么是云计算&#xff1f;云计算包含哪些关键技术&#xff1f;云计算有哪些特点&#xff1f;IaaS、PaaS、SaaS是什么&#xff1f;基础设施即服务IaaSIaaS的特点平台即服务PaaSPaaS的特点PaaS的作用软件即服务SaaSSaaS的特性云计算的基础设施和功能云计算的基础设…
暂无图片
编程学习 ·

TinyML-TVM是如何驯服Tiny的(下)

TinyML-TVM是如何驯服Tiny的&#xff08;下&#xff09; Lazy Execution 实际上&#xff0c;随着通信开销开始占主导地位&#xff0c;一旦用户请求&#xff0c;就执行算子的开销变得非常昂贵。可以通过延迟评估直到用户需要调用的结果来提高系统的吞吐量。 从实现的角度来看&a…
暂无图片
编程学习 ·

航次总结2020年7月

航次结束了快2个月&#xff0c;一直没有时间写本航次的总结&#xff0c;主要原因是自己承担的任务要验收&#xff0c;费了好大的劲&#xff0c;才将数据处理成验收单位所需要的&#xff0c;所幸现在应该是没有问题了&#xff0c;符合专项的要求了。航次期间&#xff0c;由于还有…
暂无图片
编程学习 ·

png是什么格式?

png是一种采用无损压缩算法的位图格式。PNG格式有8位、24位、32位三种形式&#xff0c;其中8位PNG支持两种不同的透明形式&#xff0c;24位PNG不支持透明&#xff0c;32位PNG在24位基础上增加了8位透明通道&#xff0c;因此可展现256级透明程度。 png是一种采用无损压缩算法的位…
暂无图片
编程学习 ·

aop 的使用

aop有两种方式&#xff0c;一个是注解&#xff0c;另外一个是配置xml 1、注解的方式&#xff1a; a、和xml一样&#xff0c;首先得有个配置&#xff0c;如果是springboot&#xff0c;写个配置类&#xff1a; Configuration EnableAspectJAutoProxy ComponentScan("xxx.…