Python进阶教程m7–混合编程–调用可执行文件

原文链接: http://www.juzicode.com/archives/1089

在计算机语言的世界里,各种编程语言百花齐放,争相斗艳,并不存在某一种语言一统天下的情景,各种语言各有其优势和应用场景,所以就存在多语言混合编程的需求,各种语言得以优势互补。比如图像处理库OpenCV的底层是效率更高的C++写的,上层提供了更友好易用的Python接口;科学计算用到的numpy底层是C和Fortran写的,这些底层库一来经过了多年的实践检验已经非常成熟,二来编译型语言效率更高,没有必要又用Python重写,直接封装好Python接口即可;另外像Python内置的sqlite3模块,也是用C语言写的底层,上层封装了Python接口。

Python内置的sqlite3模块

在Python中也有多种方法实现多语言混合编程:1.可执行文件级别的扩展,可以调用任何其他编程语言生成的可执行文件;2.可以对现有的C语言编写的动态链接库API函数实现封装;3.利用Python的扩展库实现代码级别的扩展。分别对应了文件—>API—>代码3种层级。

本文将从文件级别的扩展开始讲解Python的多语言扩展功能,接下来的几篇文章将从API和代码级别介绍其扩展功能。

1 os.popen()方法

使用popen()方法可以调用其他可执行文件或者系统命令,可执行文件输出到标准输出的内容会重新定向到一个“管道”中,调用结束后会返回一个文件对象,可以用readlines(),readline()或read()读出其内容。

比如要显示当前目录下的文件列表,在windows系统中可以直接用dir命令显示当前目录内容,我们可以用os.popen(‘dir’)实现同样的功能:

print('执行popen......')
ret = os.popen('dir')
print('执行popen结果:',ret)
print('显示popen内容:')
buffers = ret.readlines()
print(buffers)
执行popen......
执行popen结果: <os._wrap_close object at 0x00000154AA847EB8>
显示popen内容:
[' 驱动器 E 中的卷是 data\n', ' 卷的序列号是 4AC8-575E\n', '\n', ' E:\\juzicode\\py3study\\m07-调用exe 的目录\n', '\n', '2020/08/10  01:38    <DIR>          .\n', '2020/08/10  01:38    <DIR>          ..\n', '2020/08/10  01:47               601 popen-system.py\n', '               1 个文件            601 字节\n', '               2  个目录 27,096,033,280 可用字节\n']

os.popen()的第一个入参是可带入参的可执行文件或系统命令,参数类型为str类型,另外可选有mode入参,可以是’r’或’w’,默认为’r’,当mode=’r’时可以使用类似open()函数创建文件对象的read类方法,如果使用mode=’w’入参,将会在标准输出上打印执行过程中的输出内容,这时不可以再使用read类方法获取这些输出内容。

使用read类方法时也需要注意可执行文件或系统命令的某些输出虽然是可见的,但是并不一定能被read类方法读出,比如用mkdir命令创建一个已经存在的文件夹时,报错信息就不能被捕获到。下面的例子就是连续2次调用’mkdir abc’试图创建名称为abc的文件夹,第二次调用时出现失败了,这时的失败信息并不能被readlines()获取到。

另外在混合使用了printf和cout的程序中,其打印输出的顺序并非是严格对应的,如果需要解析read类方法读出的内容,并且对顺序有严格要求时需要特别注意。

使用os.popen()返回对象的close()方法可以检查popen()执行的命令是否成功,如果执行成功则返回None,否则返回其他值。但是需要注意的是使用ret=os.popen(cmd)执行某些可执行文件或者系统命令后,需要加入延时再调用ret.close()才能得到正确的结果,下面的例子还是用dir获取当前目录结构,对比是否加入延时会得到不同的返回结果,但是执行ping命令虽然耗时很长却不需要加入延时:

2 os.system()

os.system()也可以用来执行可执行文件或者系统命令,在Windows系统里,根据环境变量COMSPEC定义的shell(Windows下一般就是cmd.exe)执行命令同时会返回执行结果,这个返回结果一般是个整型变量,表示可执行文件或系统命令的退出状态,对应到C语言main()函数的return值,但是输出到stdout内容也同时打印在stdout上,并不能被Python捕获到。 os.system()方法实际上直接调用了标准C库函数system()。

print('执行system......')
ret = os.system('dir')
print('执行system结果:',ret)
执行system......
 驱动器 E 中的卷是 data
 卷的序列号是 4AC8-575E

 E:\juzicode\py3study\m07-调用exe 的目录

2020/08/10  01:38    <DIR>          .
2020/08/10  01:38    <DIR>          ..
2020/08/10  01:47               601 popen-system.py
               1 个文件            601 字节
               2 个目录 27,096,033,280 可用字节

执行system结果: 0

从上面的例子可以看到os.system()执行后返回的结果是一个整型值,不能像os.popen()那样有其他的方法获取标准输出上的内容。

3 subprocess.run()

从前面的os.popen()和os.system()可以看出,使用起来还是不太方便,os.system()不捕获stdout和stderr的打印,os.popen()在某些场合还需要等待不定时长的延时才能得到最终的结果,那有没有一种更强大的方法来解决这些问题呢?当然有了!接下来出场的是subprocess,subprocess一出现就说要替代他们,在它的规范文档PEP324中就是这么霸气宣称的:

先看一个简单的例子,还是显示当前目录下的文件清单,不过入参中需要额外增加shell=True才能在windows的cmd命令行中执行:

import subprocess
ret = subprocess.run('dir', shell=True)
print(ret)
==========结果:
 
驱动器 E 中的卷是 data
 卷的序列号是 4AC8-575E

 E:\juzicode\py3study\m07-extend-file 的目录

2020/08/12  07:55    <DIR>          .
2020/08/12  07:55    <DIR>          ..
2020/08/10  19:18               857 popen-system-test.py
2020/08/12  22:31               450 subprocess-test.py
               4 个文件          2,357 字节
               3 个目录 27,089,352,704 可用字节

CompletedProcess(args='dir', returncode=0)

也可以将输入指令和入参组成一个list或者tuple传入:

import subprocess
ret = subprocess.run(['dir'], shell=True)
ret = subprocess.run(['ping','www.baidu.com'],shell=True)

但是这种方法有非常严格的格式要求,不能有任何多余的空格。下面这个例子中将dir命令后面增加了空格,ping命令的主机参数前也增加了空格,都会导致执行执行错误:

从前面的例子可以看到,subprocess.run()返回的是一个CompletedProcess(args=’dir’, returncode=0)实例,从实例属性中可以得到执行命令的最后结果。 这个实例只包含了2个有效的属性,通过修改入参设置 capture_output=True可以捕获输出到stdout和stderr的值,得到更多属性的值。 另外如果设置了text=True,返回实例的stdout和stderr属性的值为str类型,否则为bytes类型。

import subprocess
 
print('执行 dir')
ret = subprocess.run('dir', shell=True,capture_output=True)
print('args:',ret.args)
print('returncode:',ret.returncode) 
print('stdout:',ret.stdout)
print('stderr:',ret.stderr)

print('执行 dir --notfound')
ret = subprocess.run('dir --notfound', shell=True, capture_output=True, text=True)
print('args:',ret.args)
print('returncode:',ret.returncode) 
print('stdout:',ret.stdout)
print('stderr:',ret.stderr)
==========结果:
执行 dir
args: dir
returncode: 0
stdout: b' \xc7\xfd\xb6\xaf\xc6\xf7 E \xd6\xd0\xb5\xc4\xbe\xed\xca\xc7 data\r\n \xbe\xed\xb5\xc4\xd0\xf2\xc1\xd0\xba\xc5\xca\xc7 4AC8-575E\r\n\r\n E:\\juzicode\\py3study\\m07-extend-file \xb5\xc4\xc4\xbf\xc2\xbc\r\n\r\n2020/08/13  00:55    <DIR>          .\r\n2020/08/13  00:55    <DIR>          ..\r\n2020/08/11  20:04    <DIR>          abc\r\n2020/08/11  19:11               570 popen-delay.py\r\n2020/08/11  20:03               480 popen-read-invalid.py\r\n2020/08/10  19:18               857 popen-system-test.py\r\n2020/08/13  01:16               673 subprocess-return-value.py\r\n2020/08/13  00:55               587 subprocess-test.py\r\n               5 \xb8\xf6\xce\xc4\xbc\xfe          3,167 \xd7\xd6\xbd\xda\r\n               3 \xb8\xf6\xc4\xbf\xc2\xbc 27,089,123,328 \xbf\xc9\xd3\xc3\xd7\xd6\xbd\xda\r\n'
stderr: b''

执行 dir --notfound
args: dir --notfound
returncode: 1
stdout:  驱动器 E 中的卷是 data
 卷的序列号是 4AC8-575E

 E:\juzicode\py3study\m07-extend-file 的目录


stderr: 找不到文件

从上面的例子可以看到增加capture_output=True可以将stdout和stderr的内容捕获到返回实例的stdout和stderr属性中,这时在命令行中将看不到这些输出内容。

我们又回到最开始os.popen()执行mkdir abc命令时,当文件夹已经存在的情况下,未能捕获到提示出错的内容,我们用subprocess.run()方式实验下,可以看到出错的打印已经被捕获到返回实例的stderr属性中了:

print('执行 mkdir abc') #假设已经存在了abc文件夹
ret = subprocess.run(' mkdir abc', shell=True, capture_output=True, text=True)
print('args:',ret.args)
print('returncode:',ret.returncode) 
print('stdout:',ret.stdout)
print('stderr:',ret.stderr)
=========结果:
执行 mkdir abc
args:  mkdir abc
returncode: 1
stdout:
stderr: 子目录或文件 abc 已经存在。

本文介绍了几种在Python中执行可执行文件或者系统命令的方法,这几种方法比较起来,subprocess.run()一次调用多种结果同时返回,使用起来更方便。

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注