一、项目概述平时工作常常能遇到一个python模块可以直接在命令行执行,比如pip, tensorboard, jupyter, tqdm等,好奇之余,仔细研究了一下原理和机制,发现这技术大有文章可做。
需要用到的工具:subprocess
: 可以使python执行命令行命令fire
: 使python的各种组件变为命令行setuptools
: 创建和分发Python模块,尤其是拥有依赖关系的。 二、 subprocess
模块简介 2.1、概述在Python中,提供了subprocess模块,通过这个模块中的相应API,就可以开启一个子进程来执行相应的脚本来完成这个操作。 可以通过subprocess中的Popen类来处理这个命令。 即允许你去创建一个新的进程让其执行另外的程序,并与它进行通信,获取标准的输入、标准输出、标准错误以及返回码等。 2.2、相关函数说明函数 说明 subprocess.run() Python 3.5中新增的函数。执行指定的命令,等待命令执行完成后返回一个包含执行结果的CompletedProcess类的实例 subprocess.call() 执行指定的命令,返回命令执行状态,其功能类似于os.system(cmd) subprocess.check_call() Python 2.5中新增的函数。 执行指定的命令,如果执行成功则返回状态码,否则抛出异常。其功能等价于subprocess.run(…, check=True) subprocess.check_output() Python 2.7中新增的的函数。执行指定的命令,如果执行状态码为0则返回命令执行结果,否则抛出异常 subprocess.getoutput(cmd) 接收字符串格式的命令,执行命令并返回执行结果,其功能类似于os.popen(cmd).read()和commands.getoutput(cmd) subprocess.getstatusoutput(cmd) 执行cmd命令,返回一个元组(命令执行状态, 命令执行结果输出) subprocess. Popen() 实际上,上面的几个函数都是基于Popen()的封装(wrapper)。这些封装的目的在于让我们容易使用子进程。当我们想要更个性化我们的需求的时候,就要转向Popen类,该类生成的对象用来代表子进程。
2.3、示例代码 2.3.1、执行pwd命令参考博文:彭世瑜
1 2 3 4 5 6 7 8 9 10 11 12 13 import subprocessclass CommandException (Exception) : """自定义错误提示类型""" pass def run_cmd (command) : return_code, output = subprocess.getstatusoutput(command) if return_code != 0 : raise CommandException(output) return output if __name__ == '__main__' : print("output: " , run_cmd("pwd" ))
输出结果:
1 /Users/haibei/Documents/mono
2.3.2、输出结果实时打印使用了 subprocess.getstatusoutput
会发现一个问题,命令返回的提示信息,要等待命令全部结束才能返回,这样的交互不是特别友好和及时,可以通过以下方法进行调整
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import subprocessclass CommandException (Exception) : """自定义错误提示类型""" pass def run_cmd (command) : p = subprocess.Popen(command, shell=True , stdout=subprocess.PIPE, stderr=subprocess.STDOUT) while p.poll() is None : line = p.stdout.readline() line = line.decode('ascii' ).strip() if line: print(line) if p.returncode != 0 : raise CommandException("命令执行有误" )
三、 fire
模块简介fire
库是Google公司提供开源库,可以快速将python函数转为命令行模式
1 2 3 4 5 6 7 import firedef hello (name) : return 'Hello {name}!' .format(name=name) if __name__ == '__main__' : fire.Fire()
在命令行中运行:
1 2 $ python example.py hello World Hello World!
3.1、通过class构造多种带参数的命令形式当构建class类时,其实例化参数可以作为命令行参数来引用,形式为 --flag
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import fireclass 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 $ python example.py add 10 20 31 $ python example.py multiply 10 20 --offset=0 200
因为 setuptools
的配置过于庞大,这里只介绍 entry_points
,其它功能不进行赘述。 entry_points
参数可以将python模块转变为命令行工具,比如我们平时常用的 pip
命令,就是被 entry_points
转变命令脚本放入系统bin目录下,其源码如下:1 2 3 4 5 6 import reimport sysfrom pip._internal.main import mainif __name__ == '__main__' : sys.argv[0 ] = re.sub(r'(-script\.pyw?|\.exe)?$' , '' , sys.argv[0 ]) sys.exit(main())
1 2 3 4 5 6 entry_points={ "console_scripts" : [ "pip=pip._internal.main:main" , "pip{}=pip._internal.main:main" .format(sys.version_info[0 ]), "pip{}.{}=pip._internal.main:main" .format(*sys.version_info[:2 ]), ],
所以当一个项目按照上面的配置方法进行配置以后,就会生产一个系统命令,帮我们快速执行程序 五、制作自己的命令我们都知道,pip命令在国内使用,需要镜像站进行加速,虽然可以通过配置文件(参考我的博文 )进行永久配置,但一旦变更环境还需要重新进行繁琐的配置工作。 综合上面的技术,我们可以通过自定义系统命令,将加速的镜像源地址写入程序,以达到加速的目的。
5.1、相关代码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 import fireimport subprocessclass CommandException (Exception) : pass def run_cmd (command) : p = subprocess.Popen(command, shell=True , stdout=subprocess.PIPE, stderr=subprocess.STDOUT) while p.poll() is None : line = p.stdout.readline() line = line.decode('ascii' ).strip() if line: print(line) if p.returncode != 0 : raise CommandException("命令执行有误" ) class FastPip (object) : def __init__ (self, u=False) : self.info, self.arg = ("升级" , "-U " ) if u else ("安装" , "" ) self.mirror = "https://mirrors.huaweicloud.com/repository/pypi/simple" def install (self, name) : mirror = f" -i {self.mirror} " print(f"开始{self.info} :{name} " ) if name.split("." )[-1 ] == "git" : name = "git+" + name mirror = "" return run_cmd( f"pip install {self.arg} {name} {mirror} --no-cache-dir" ) if __name__ == '__main__' : fire.Fire(FastPip)
setup.py的配置
1 2 3 4 5 entry_points={ 'console_scripts' : [ 'fpip = magic.terminal:main' ] }
5.2、使用方法标准安装: fpip install torch
升级安装: fpip install torch --u
github安装: fpip install https://github.com/pypa/pip.git
github升级安装: fpip install https://github.com/pypa/pip.git --u