首页 > 编程语言 >python解释器源码函数调用分析

python解释器源码函数调用分析

时间:2024-07-22 16:40:52浏览次数:10  
标签:LOAD 函数 python Py 函数调用 PyObject 源码 func op

1、编译python代码

1.1 python代码

test.py

1 def ftest():
2     x = 3
3 ftest()

1.2 编译工具

disass_py.py

#-*- coding:utf8 -*-
import dis
import sys

def disassemble_file(file_path):
    with open(file_path, 'r') as file:
        source_code = file.read()
    try:
        compiled_code = compile(source_code, file_path, 'exec')
        dis.dis(compiled_code)
    except Exception as e:
        print("Error disassembling {}: {}".format(file_path, e))

if __name__ == '__main__':
    if len(sys.argv) != 2:
        print("Usage: python disass_py.py <path_to_python_file>")
        sys.exit(1)

    file_path = sys.argv[1]
    disassemble_file(file_path)

1.3 编译python代码

root(127.0.0.1) /data/ # python3 ./disass_py.py ./test.py
  1           0 LOAD_CONST               0 (<code object ftest at 0x7f34cb4b49d0, file "./test.py", line 1>)
              2 LOAD_CONST               1 ('ftest')                      // 将 ftest 载入
              4 MAKE_FUNCTION            0                                // 构建 ftest 函数
              6 STORE_NAME               0 (ftest)                        // 将 ftest 函数结构体存入 locals

  3           8 LOAD_NAME                0 (ftest)                        // 从 locals 中加载 ftest 压栈
             10 CALL_FUNCTION            0                                // 调用栈顶函数
             12 POP_TOP
             14 LOAD_CONST               2 (None)
             16 RETURN_VALUE

Disassembly of <code object ftest at 0x7f34cb4b49d0, file "./xtest.py", line 1>:    // 函数实现
  2           0 LOAD_CONST               1 (3)
              2 STORE_FAST               0 (x)
              4 LOAD_CONST               0 (None)
              6 RETURN_VALUE

 

2、分析对应字节码

可以看见python代码非常简单,定义了一个函数,并调用它。

整个字节码也体现的很清晰,

  1. 载入函数名称        2 LOAD_CONST               1 ('ftest')
  2. 构建函数               4 MAKE_FUNCTION            0
  3. 存入函数               6 STORE_NAME               0 (ftest)
  4. 取出栈顶函数        8 LOAD_NAME                0 (ftest)
  5. 调用函数               10 CALL_FUNCTION            0

2.1 LOAD_CONST

        case TARGET(LOAD_CONST): {
            PREDICTED(LOAD_CONST);
            PyObject *value = GETITEM(consts, oparg);
            Py_INCREF(value);
            PUSH(value);
            DISPATCH();
        }

2.2 MAKE_FUNCTION

解释器主循环中调用 PyFunction_NewWithQualName 将函数名称,代码对象弹栈,构建一个python函数。

        case TARGET(MAKE_FUNCTION): {
            PyObject *qualname = POP();
            PyObject *codeobj = POP();
            PyFunctionObject *func = (PyFunctionObject *)
                PyFunction_NewWithQualName(codeobj, f->f_globals, qualname);

            Py_DECREF(codeobj);
            Py_DECREF(qualname);
            if (func == NULL) {
                goto error;
            }

            if (oparg & 0x08) {
                assert(PyTuple_CheckExact(TOP()));
                func->func_closure = POP();
            }
            if (oparg & 0x04) {
                assert(PyTuple_CheckExact(TOP()));
                func->func_annotations = POP();
            }
            if (oparg & 0x02) {
                assert(PyDict_CheckExact(TOP()));
                func->func_kwdefaults = POP();
            }
            if (oparg & 0x01) {
                assert(PyTuple_CheckExact(TOP()));
                func->func_defaults = POP();
            }

            PUSH((PyObject *)func);  // 将函数结构体压栈
            DISPATCH();
        }

2.2.1 PyFunction_NewWithQualName

创建与python函数代码绑定的 PyFunctionObject 结构体,在 op->vectorcall = _PyFunction_Vectorcall; 中将_PyFunction_Vectorcall 用于引导调用python 函数。

PyObject *
PyFunction_NewWithQualName(PyObject *code, PyObject *globals, PyObject *qualname)
{
    ...
    PyThreadState *tstate = _PyThreadState_GET();

    PyCodeObject *code_obj = (PyCodeObject *)code;  // 函数代码
    ...
    PyObject *name = code_obj->co_name;             // 函数名称
    ...
    // __module__: Use globals['__name__'] if it exists, or NULL.
    _Py_IDENTIFIER(__name__);
    PyObject *module = _PyDict_GetItemIdWithError(globals, &PyId___name__);
    PyObject *builtins = NULL;
    ...
    builtins = _PyEval_BuiltinsFromGlobals(tstate, globals); // borrowed ref
    if (builtins == NULL) {
        goto error;
    }
    Py_INCREF(builtins);
    PyFunctionObject *op = PyObject_GC_New(PyFunctionObject, &PyFunction_Type);
    ...
    op->func_globals = globals;
    op->func_builtins = builtins;
    op->func_name = name;                 // 函数名称
    op->func_qualname = qualname;
    op->func_code = (PyObject*)code_obj;  // 函数代码
    op->func_defaults = NULL;    // No default positional arguments
    op->func_kwdefaults = NULL;  // No default keyword arguments
    op->func_closure = NULL;
    op->func_doc = doc;
    op->func_dict = NULL;
    op->func_weakreflist = NULL;
    op->func_module = module;
    op->func_annotations = NULL;
    op->vectorcall = _PyFunction_Vectorcall; // Vectorcall 函数

    _PyObject_GC_TRACK(op);
    return (PyObject *)op;

error:
    ...
}

2.3 STORE_NAME

存储指定名称的python对象到当前函数帧的locals 中。

        case TARGET(STORE_NAME): {
            PyObject *name = GETITEM(names, oparg);
            PyObject *v = POP();
            PyObject *ns = f->f_locals;
            int err;
            ...
            if (PyDict_CheckExact(ns))
                err = PyDict_SetItem(ns, name, v);   // 将名称映射的对象存入当前frame 的locals
            else
                err = PyObject_SetItem(ns, name, v); // 将名称映射的对象存入当前frame 的locals
            Py_DECREF(v);
            ..
            DISPATCH();
        }

2.4 LOAD_NAME

从当前帧的locals 中找到指定名称的python对象,并压入栈。

        case TARGET(LOAD_NAME): {
            PyObject *name = GETITEM(names, oparg);
            PyObject *locals = f->f_locals;
            PyObject *v;
            if (PyDict_CheckExact(locals)) {
                ...
            }else {
                v = PyObject_GetItem(locals, name);   // 从locals 中获取指定名称的对象
                ...
            }
            ...
            PUSH(v);                                 // 将其压入栈顶
            DISPATCH();
        }

2.5 CALL_FUNCTION

调用栈顶的函数

        case TARGET(CALL_FUNCTION): {
            PREDICTED(CALL_FUNCTION);
            PyObject **sp, *res;
            sp = stack_pointer; // 栈顶指针
            res = call_function(tstate, &trace_info, &sp, oparg, NULL); // call 指定函数
            stack_pointer = sp;
            PUSH(res);
            if (res == NULL) {
                goto error;
            }
            CHECK_EVAL_BREAKER();
            DISPATCH();
        }

2.5.1 call_function

从栈中取出参数,函数结构体,引导执行该函数。

Py_LOCAL_INLINE(PyObject *) _Py_HOT_FUNCTION
call_function(PyThreadState *tstate,
              PyTraceInfo *trace_info,
              PyObject ***pp_stack,
              Py_ssize_t oparg,
              PyObject *kwnames)
{
    PyObject **pfunc = (*pp_stack) - oparg - 1;  // 减去栈顶的操作码,再减去一个指针的大小,得到上一个指令 LOAD_NAME 从local 中加载的指定函数结构体
    PyObject *func = *pfunc;
    PyObject *x, *w;
    Py_ssize_t nkwargs = (kwnames == NULL) ? 0 : PyTuple_GET_SIZE(kwnames);
    Py_ssize_t nargs = oparg - nkwargs;
    PyObject **stack = (*pp_stack) - nargs - nkwargs;

    x = PyObject_Vectorcall(func, stack, nargs | PY_VECTORCALL_ARGUMENTS_OFFSET, kwnames); // 执行vectorcall函数调用

    /* Clear the stack of the function object. */
    while ((*pp_stack) > pfunc) {
        w = EXT_POP(*pp_stack);
        Py_DECREF(w);
    }

    return x;
}

2.5.2 查找执行引导函数

 

static inline PyObject *
PyObject_Vectorcall(PyObject *callable, PyObject *const *args,
                     size_t nargsf, PyObject *kwnames)
{
    PyThreadState *tstate = PyThreadState_Get();
    return _PyObject_VectorcallTstate(tstate, callable,
                                      args, nargsf, kwnames);   // 找到 vectorcall 并执行 
}


static inline PyObject *
_PyObject_VectorcallTstate(PyThreadState *tstate, PyObject *callable,
                           PyObject *const *args, size_t nargsf,
                           PyObject *kwnames)
{
    vectorcallfunc func;
    PyObject *res;
    ...
    func = PyVectorcall_Function(callable);    // 找到结构体中的 vectorcall 函数
    ...
    res = func(callable, args, nargsf, kwnames); // 执行 vectorcall 函数
    return _Py_CheckFunctionResult(tstate, callable, res, NULL);
}

static inline vectorcallfunc
PyVectorcall_Function(PyObject *callable)
{
    PyTypeObject *tp;
    Py_ssize_t offset;
    vectorcallfunc ptr;

    tp = Py_TYPE(callable);              // cast 成pyobj 类型
    ....
    offset = tp->tp_vectorcall_offset;  // 获取 vectorcall 函数在结构体中的偏移大小
    assert(offset > 0);
    memcpy(&ptr, (char *) callable + offset, sizeof(ptr)); // 找到 vectorcall 函数 将其复制到 ptr函数指针
    return ptr;  // 将其返回
}

 

标签:LOAD,函数,python,Py,函数调用,PyObject,源码,func,op
From: https://www.cnblogs.com/shiqi17/p/18316343

相关文章

  • python学习笔记——基础数据类型
    一、python赋初值         1.Python中的变量不需要声明。每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建。    2.在Python中,变量就是变量,它没有类型,我们所说的"类型"是变量所指的内存中对象的类型。    3.等号(=)用来给变量赋值。 ......
  • Python数据可视化常用的库
    Python中的数据可视化是指使用图形和图表来展示数据,以便更直观地理解和分析数据。数据可视化的目的是将复杂的数据转化为容易理解的视觉形式,从而帮助发现数据中的模式、趋势和异常情况。以下是数据可视化的一些主要用途:探索性数据分析:帮助理解数据分布和结构识别数据中的......
  • 张高兴的 MicroPython 入门指南:(三)使用串口通信
    目录什么是串口使用方法使用板载串口相互通信硬件需求电路代码使用板载的USB串口参考什么是串口串口是串行接口的简称,这是一个非常大的概念,在嵌入式中串口通常指UART(UniversalAsynchronousReceiver/Transmitter,通用异步收发器)。使用串口进行的通信叫做串行通信,与之相对的一......
  • 如何使用 Python 自动反转 .cal 和 .GP4 图像文件中的颜色?
    我在.cal和.GP4中有数千个计划,我需要反转其颜色(当它们处于“负”时切换到“正”模式)。我知道可以在像autocad这样的软件中一一完成,但出于明显节省时间的原因,我正在寻找一种批量处理方法。我创建了一个Python程序来执行该操作,但先验有没有允许轻松操作.cal和.GP4......
  • 写一个 python daemo 注册到nacos中
     """注册到nacos中的deamonnacos:2.3.2(模式:standalone)python:3.6.8nohuppython3demon.py&"""importrequestsimportthreadingimporttime#Nacos服务器地址和端口nacos_url="http://127.0.0.1:8848"#Nacos登录信息user......
  • Python、图形用户界面、ctk
    所以,我正在创建一个博客,现在,我在设置部分,我有一个带有按钮的滑动面板,我希望它转到一个新窗口,我将在其中创建新的小部件等...,我已经完成了这种登录和注册的事情,问题是现在我不能使用pack.forget(),它只是不起作用classSlidePanel(customtkinter.CTkFrame):def__init__(se......
  • 当我的代码损坏时,如何设置警报或蜂鸣声? (最好是Python)
    我正在为机器人运行一些代码,它将继续运行,直到我手动终止该进程。或者,如果代码意外遇到诸如SYntaxError或其他此类错误/异常之类的错误并崩溃。我想知道当我的代码崩溃时是否可以设置一些警报或蜂鸣声。我的目标就是将视线从屏幕上移开,仅在进程停止运行时才检查它。如果......
  • 手撕数据结构01--单链表(附源码)
    目录1.链表的概念和结构1.1链表的概念1.2单链表结构2.实现单链表2.1节点定义2.2链表功能2.3 创建节点2.4尾插2.5头插2.6打印2.7尾删2.8头删2.9查找2.10指定位置插入2.11指定位置之后插入2.12删除pos节点2.13删除pos之后的节点2.14销毁链表......
  • VScode连接虚拟机运行Python文件的方法
    声明:本文使用Linux发行版本为rocky_9.4目录1.在rocky_9.4最小安装的系统中,默认是没有tar工具的,因此,要先下载tar工具2.在安装好的vscode中下载ssh远程插件工具3.然后连接虚拟机4.查看python是否已经安装5.下载扩展插件6.新建.py文件测试1.在rocky_9.4最小安装......
  • 【介绍Python多进程】
    ......