python 中 os 模块用得比较多,但 os.system 实际上是怎么调用 shell 命令的呢?简单来探寻一下。
1、系统环境
macos 10.15.6 x86_64
python 3.8.5
为什么要强调系统环境,因为 python 在不同系统版本上实现可能会有差异,待会讲解就能发现了。
2、os 模块
通过 help(os) 可以找到源文件查看,
下面截取相关代码来看(直接用注释解释了):
# 返回一个包含内建模块名字的元组,包含所有已经编译到Python解释器的模块名字
_names = sys.builtin_module_names
# __all__ 只影响到了 from <module> import * 这种导入方式
def _get_exports_list(module):
try:
return list(module.__all__)
except AttributeError:
return [n for n in dir(module) if n[0] != '_']
# 判断当前操作系统类型
# posix代表类Unix系统,nt表示Windows系统
# 我们的 macOS 就是类Unix系统,只截取这部分代码
if 'posix' in _names:
name = 'posix' # 表示操作系统类型
linesep = '\n' # 定义了当前平台使用的行终止符
# os模块其实就是对posix或nt模块的二次封装,这样的好处就是实现了对不同平台的兼容
from posix import *
try:
from posix import _exit
__all__.append('_exit')
except ImportError:
pass
import posixpath as path # 我们常用的os.path实际上是ntpath或者posixpath模块
try:
from posix import _have_functions
except ImportError:
pass
import posix
__all__.extend(_get_exports_list(posix))
del posix
现在我们知道了在 macos 平台上,os 模块实际上是对 posix 模块的封装。
2.1、subprocess 模块
顺道再看看 subprocess 调用的实现:
if shell: #制作 shell 的参数
args = ["/bin/sh", "-c"] + args
if executable:
args[0] = executable
if executable is None:
executable = args[0]
# ··· ···
self.pid = os.fork() #创建子进程
# ··· ···
os.execvp(executable, args) #执行 shell 命令
3、posix 模块
posix 即 Portable Operating System Interface(可移植操作系统接口),是类 Unix 系统的兼容标准。
同样通过 help(posix) 看下是否有源文件,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BVF4365W-1603002971090)(https://upload-images.jianshu.io/upload_images/13705657-bdc86b0996e50a78?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
可以看到 posix 是 built-in module,代表是直接编译好放在 python 这个可执行文件里的。
可以在 module 的 config.c 中找到相关的转接代码。
extern void initposix(void);
struct _inittab _PyImport_Inittab[] = {
{"posix", initposix},
// ...
3.1、对 built-in 模块的继续查找
实际上对于 built-in 模块还能继续查找,首先要弄清楚当前 python 的虚拟机是用什么实现的:
可以看到 macos 系统 python 虚拟机是 CPython 实现的。
CPython 是开源的,那么我们可以从 cpython 中找到相关的源码:
static PyMethodDef posix_methods[] = {
... ...
#ifdef HAVE_SYSTEM
{"system", posix_system, METH_VARARGS, posix_system__doc__},
#endif
... ...
};
#ifdef HAVE_SYSTEM
PyDoc_STRVAR(posix_system__doc__,
"system(command) -> exit_status\n\n\
Execute the command (a string) in a subshell.");
// 在Python中操作的任何元素都是一個由C語言實現的PyObject對象
static PyObject *
posix_system(PyObject *self, PyObject *args)
{
char *command;
long sts;
if (!PyArg_ParseTuple(args, "s:system", &command))
return NULL;
Py_BEGIN_ALLOW_THREADS // 允许其他线程获取 GIL 锁来跑
sts = system(command); // system call(或其他不会操作Python Data的调用)
Py_END_ALLOW_THREADS // 重新获取 GIL 锁
return PyInt_FromLong(sts);
}
#endif
可以看到 posix.system 最终会调用 C 的 system 函数。
C 的 system 函数就不需要再深入进去了,在 C/C++ 网站有很多介绍。
4、结论
macos 系统下:
os.system -> posix.system -> C 的 system 函数