目录
  1. 一、项目概述
  2. 二、 subprocess 模块简介
    1. 2.1、概述
    2. 2.2、相关函数说明
    3. 2.3、示例代码
      1. 2.3.1、执行pwd命令
      2. 2.3.2、输出结果实时打印
  3. 三、 fire 模块简介
    1. 3.1、通过class构造多种带参数的命令形式
  4. 四、 setuptools 模块简介
  5. 五、制作自己的命令
    1. 5.1、相关代码
    2. 5.2、使用方法
【技术】新世界的大门:将python程序变为系统脚本命令

一、项目概述

平时工作常常能遇到一个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 subprocess
class CommandException(Exception):
"""自定义错误提示类型"""
pass

def run_cmd(command):
return_code, output = subprocess.getstatusoutput(command)
if return_code != 0: # 返回的状态码为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 subprocess

class CommandException(Exception):
"""自定义错误提示类型"""
pass

def run_cmd(command):
p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while p.poll() is None: # 通过poll方法对实时产生信息的循环打印
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 fire

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

在命令行中运行:

1
2
3
4
$ python example.py add 10 20
31
$ python example.py multiply 10 20 --offset=0
200

四、 setuptools 模块简介

  • 因为 setuptools 的配置过于庞大,这里只介绍 entry_points ,其它功能不进行赘述。
  • entry_points 参数可以将python模块转变为命令行工具,比如我们平时常用的 pip 命令,就是被 entry_points 转变命令脚本放入系统bin目录下,其源码如下:
1
2
3
4
5
6
import re
import sys
from pip._internal.main import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(main())
  • pipsetup.py 文件的配置
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 fire
import subprocess

class 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

文章作者: Haibei
文章链接: http://www.haibei.online/posts/3828475579.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Haibei的博客
打赏
  • 微信
  • 支付宝

评论