前言node
本篇教程的目的是但愿你们能够通读完此篇以后,可使用python制做一款符合本身需求的linux工具。python
本教程使用的是google开源的python第三方库:firelinux
不管是学生党本身作着练手,仍是工做中确有需求,本篇都尽量经过简单的例子来示范该第三方库的用法,其中如有描述不当的地方,望留言指出。git
来一波官方介绍。github
- Python Fire是一个库,用于从任何Python对象自动生成命令行接口。
- 是用python建立CLI的一种简单方法。
- 是开发和调试Python代码的一个有用工具。
- Python Fire帮助探索现有代码或将其余人的代码转换为CLI。
- 使得Bash和Python之间的转换更加容易。
- 经过使用已经导入和建立的模块和变量来设置REPL, Python Fire使使用Python REPL变得更容易。
没听懂 ???shell
不是太明白 ???express
没关系,看完本篇就懂了。canvas
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-firebash
实践出真知服务器
建立一个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 命令行工具备很大的区别:
每次跑命令都要再敲一个python
每次还要指向指定的py文件或到指定的目录下
首先说第一个问题,每次多敲六个字母和一个空格,做为一个linux命令行工具是很是不合格的,原本命令行工具就在追求简单化,这种指定解释器的操做咱们固然要尽量省掉咯
第二个问题,总是指定文件的目录就更麻烦了,平常使用的时候在不一样的服务器跑命令还要想一想放在哪里,并且若是使用绝对路径的话,更会致使命令的冗长。
下面咱们来解决一下这两个“小”问题:
#!/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)
root@node:~# chmod +x test.py
root@node:~# mv test.py mytool
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大法去完成的事情。甚至有一些逻辑,想要实现还要本身去作更复杂的逻辑。
此时,一年级的已经能够下课了,二年级的请注意听讲了,下面,咱们要讲的是:
类的使用
命令嵌套
属性访问
链式调用
经过一个简单的算数类来了解其用法,在下列用法中,咱们在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
经过不一样的类来控制某些同名命令,其实也是将各个命令分门别类更具条理性的管理。能够看到如下用法。
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类,咱们能够本身组合想要的命令行效果,给子命令再分配不一样的子集。
其实前面说到类的时候已经简单的说过属性访问(就是那个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
综上,咱们能够经过控制类的属性来构造类对象。
唠到这儿了,再唠一个骚操做
官方给的例子不太好看,没有那么让人一眼就看懂的感受,找了个四则运算的简单示例:
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
中用来完成自定义序列化。若是不提供这个方法,在链式调用完成后将会打印帮助内容。
# 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 的内置参数吧,具体怎么应用你们就本身研究咯。
Using a CLI | Command | Notes |
---|---|---|
Help | command -- --help |
显示命令的帮助和使用信息。 |
REPL | command -- --interactive |
进入交互模式。 |
Separator | command -- --separator=X |
这将分隔符设置为' X '。默认分隔符是“-”。 |
Completion | command -- --completion [shell] |
为CLI生成一个补全的shell脚本。 |
Trace | command -- --trace |
跟踪fire命令调用后发生了啥子。 |
Verbose | command -- --verbose |
在输出中包含私有成员。 |