python 中 os 模块用得比较多,但 os.system 实际上是怎么调用 shell 命令的呢?简单来探寻一下。

1、系统环境

macos 10.15.6      x86_64
python 3.8.5

为什么要强调系统环境,因为 python 在不同系统版本上实现可能会有差异,待会讲解就能发现了。

2、os 模块

通过 help(os) 可以找到源文件查看,

python手动实现阻塞 python os.system阻塞_macos


下面截取相关代码来看(直接用注释解释了):

# 返回一个包含内建模块名字的元组,包含所有已经编译到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 的虚拟机是用什么实现的:

python手动实现阻塞 python os.system阻塞_python手动实现阻塞_02

可以看到 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 函数