『Python底层原理』--CPython 虚拟机

news/2025/1/23 17:23:38/文章来源:https://www.cnblogs.com/wang_yb/p/18688238

Python 编程的世界里,我们每天都在使用 python 命令运行程序,但你是否曾好奇这背后究竟发生了什么?

本文将初步探究 CPythonPython 中最流行的实现)的一些内部机制,为了更好的来理解 Python 语言的底层运作。

1. CPython 简介

CPython 是用** C 语言**编写的 Python 解释器,在众多 Python 实现(如 PyPyJythonIronPython 等)中,它以其原创性、良好的维护性和高人气脱颖而出。

了解 CPython 的一些内部机制,对我们学习和使用Python语言本身也有很大的帮助:

  1. 有助于深入理解 Python 语言:了解实现细节能让我们更轻松地掌握 Python 的一些特性
  2. 实现细节在实际应用中至关重要:对象存储方式、垃圾回收机制以及多线程协调等方面的知识,对于理解语言的适用性、局限性、性能评估和效率检测都非常关键
  3. CPython 提供的 Python/C API 允许我们用 C 扩展 Python 或在 C 中嵌入 Python,而有效使用该 API 需要对 CPython 的工作原理有深入理解

CPython是开源的,源码在github.com上:https://github.com/python/cpython

每个版本的Python都有相应的CPython实现,我目前使用的Python3.12

所以本文后续如果有参考的代码,参考的是CPython 3.12分支中的代码。

2. Python执行的流程

宏观上来来看,一个Python程序的执行大致分为三个阶段:

第一个阶段是初始化阶段CPython 在此阶段初始化运行 Python 所需的数据结构,包括内置类型、配置和加载内置模块、设置导入系统等。

这个阶段虽然重要,但常常被忽视,因为它主要为程序的运行做一些准备工作。

第二个阶段是编译阶段CPython是解释器,虽不生成机器码,但会将源代码转换为中间表示形式。

它会解析源代码构建抽象语法树(AST),从 AST 生成字节码,并进行一些字节码优化。

这个阶段虽然名称是编译,但是和C/C++这类编译型语言的编译不是一个含义。

Python代码经过编译之后的字节码是可以查看的,比如下面简单写一个加法函数。

def add(x, y):return x + y

命令行中使用:python.exe -m dis .\cpython-vm.py查看字节码。

LOAD_FAST 指令将局部变量压入栈中,

BINARY_ADD 指令从栈中弹出两个对象,将它们相加,并将结果压回栈中。

最后,RETURN_VALUE 指令弹出栈顶的任何内容,并将结果返回给调用者。

最后一个阶段是解释阶段CPython 的核心是一个执行字节码的虚拟机。字节码是一系列指令,每条指令由一个操作码和一个参数组成。

CPython 虚拟机是基于栈的,通过栈来存储和检索数据,执行指令。字节码执行在一个巨大的求值循环中进行,直到没有指令可执行或发生错误。

3. 核心概念

通过CPython虚拟机的内部机制来了解Python的底层原理,首先要关注的就是CPython虚拟机中的一些核心概念。包括:代码对象函数对象帧对象

3.1. 代码对象

代码对象CPython 中存储代码块相关信息的结构,像模块、函数体这类作为独立执行单元的代码,其信息都保存在代码对象里。

它包含字节码(程序编译后的中间表示形式),以及代码块内使用的变量名列表等关键信息。

从本质上讲,代码对象是对一段可执行代码的抽象表示,为函数的调用、模块的运行提供了必要的指令和数据描述。

其相关定义在源码文件:cpython/Include/cpython/code.h

#define _PyCode_DEF(SIZE) {                                                    \PyObject_VAR_HEAD                                                          \// 省略...                                                                 \/* The hottest fields (in the eval loop) are grouped here at the top. */   \PyObject *co_consts;           /* list (constants used) */                 \PyObject *co_names;            /* list of strings (names used) */          \PyObject *co_exceptiontable;   /* Byte string encoding exception handling  \table */                                 \int co_flags;                  /* CO_..., see below */                     \\/* The rest are not so impactful on performance. */                        \int co_argcount;              /* #arguments, except *args */               \int co_posonlyargcount;       /* #positional only arguments */             \int co_kwonlyargcount;        /* #keyword only arguments */                \int co_stacksize;             /* #entries needed for evaluation stack */   \int co_firstlineno;           /* first source line number */               \\/* redundant values (derived from co_localsplusnames and                   \co_localspluskinds) */                                                  \int co_nlocalsplus;           /* number of spaces for holding local, cell, \and free variables */                     \int co_framesize;             /* Size of frame in words */                 \int co_nlocals;               /* number of local variables */              \int co_ncellvars;             /* total number of cell variables */         \int co_nfreevars;             /* number of free variables */               \uint32_t co_version;          /* version number */                         \\PyObject *co_localsplusnames; /* tuple mapping offsets to names */         \PyObject *co_localspluskinds; /* Bytes mapping to local kinds (one byte    \per variable) */                          \PyObject *co_filename;        /* unicode (where it was loaded from) */     \PyObject *co_name;            /* unicode (name, for reference) */          \PyObject *co_qualname;        /* unicode (qualname, for reference) */      \PyObject *co_linetable;       /* bytes object that holds location info */  \PyObject *co_weakreflist;     /* to support weakrefs to code objects */    \// 省略...
}/* Bytecode object */
struct PyCodeObject _PyCode_DEF(1);

定义比较长,这里只列出了一部分。

以上一节中示例中的函数add(x, y)函数为例,CPython 会为add函数体创建一个代码对象。

在这个代码对象中,字节码部分记录了如何加载变量xy、执行加法操作返回结果等指令序列。

同时,还包含co_argcount(参数数量,此处为 2)等属性,这些属性描述了函数的参数使用情况。

此外,代码对象还会记录函数定义所在的文件名(co_filename )、起始行号(co_firstlineno)等信息,方便调试和代码分析。

3.2. 函数对象

函数对象不仅仅包含可执行代码(即代码对象),还存储了与函数相关的其他重要信息,如函数名文档字符串docstring)、默认参数外部作用域变量值等。

函数对象将代码对象与函数运行所需的上下文信息整合在一起,使得函数可以在不同的环境中被正确调用和执行。

多个函数对象可以引用同一个代码对象,通过不同的外部信息实现不同的功能,例如闭包的实现。

其相关定义在源码文件:cpython/Include/cpython/funcobject.h

typedef struct {PyObject_HEAD_Py_COMMON_FIELDS(func_)PyObject *func_doc;         /* The __doc__ attribute, can be anything */PyObject *func_dict;        /* The __dict__ attribute, a dict or NULL */PyObject *func_weakreflist; /* List of weak references */PyObject *func_module;      /* The __module__ attribute, can be anything */PyObject *func_annotations; /* Annotations, a dict or NULL */PyObject *func_annotate;    /* Callable to fill the annotations dictionary */PyObject *func_typeparams;  /* Tuple of active type variables or NULL */vectorcallfunc vectorcall;uint32_t func_version;} PyFunctionObject;

3.3. 帧对象

帧对象CPython 中用于跟踪代码执行过程中的各种状态信息。

当虚拟机执行代码对象时,帧对象负责记录变量的值、维护值栈(用于指令执行时的数据存储和操作),还会记录代码执行的位置(如当前行号、上一条执行的指令位置等),以便在函数调用、返回以及异常处理等情况下,能够正确恢复和继续执行代码。

可以说,帧对象代码对象的执行提供了一个动态的上下文环境,它随着代码的执行而创建和销毁,形成一个调用栈,反映了函数调用的层次结构。

其相关定义在源码文件:cpython/Include/internal/pycore_frame.h

struct _frame {PyObject_HEADPyFrameObject *f_back;      /* previous frame, or NULL */struct _PyInterpreterFrame *f_frame; /* points to the frame data */PyObject *f_trace;          /* Trace function */int f_lineno;               /* Current line number. Only valid if non-zero */char f_trace_lines;         /* Emit per-line trace events? */char f_trace_opcodes;       /* Emit per-opcode trace events? */PyObject *f_extra_locals;   /* Dict for locals set by users using f_locals, could be NULL *//* This is purely for backwards compatibility for PyEval_GetLocals.PyEval_GetLocals requires a borrowed reference so the actual referenceis stored here */PyObject *f_locals_cache;/* The frame data, if this frame object owns the frame */PyObject *_f_frame_data[1];
};

当调用函数时,会创建一个新的帧对象并压入调用栈。

当函数执行结束返回时,该帧对象从调用栈中弹出,虚拟机根据帧对象中记录的f_back(指向前一个帧对象的引用)等信息,恢复到调用函数之前的状态,继续执行后续代码。

4. 总结

本文主要对 CPython 执行 Python 程序的过程做一个初步的宏观介绍,了解了其主要的阶段和核心概念。

后续打算进一步就CPython某个部分的具体实现细节来介绍,逐步对CPython的内部机制进行深入的了解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/873771.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

销售转化关键:掌握真诚沟通技巧

在日常沟通中,我特别不喜欢别人一上来就问“在吗”,或者只是单纯地发一个“微笑”表情。这种开场方式,总觉得少了点诚意和温度。 如果真的有事需要找人帮忙,与其冷冰冰地问“在吗”,不如先亲切地喊出对方的名字或昵称,然后再直截了当地说明来意。这样不仅能让对方感受到你…

万不得已,千万不要去外包

之前的失业日志系列里有讲,现在的就业环境不太好,有面试机会的基本都是外包岗位。刚好之前公司和外包有几次合作,主要是把一部分或者整个项目外包的外包公司,我们技术人员负责一部分的技术支持,通过几次的合作,对外包的工作也有可一个大概的了解,想谈一下自己对外包的了…

杂项-在vscdoe上使用jupyter notebook写javascript

目录准备安装notebook对javascript的支持进一步优化支持es6安装npm包将notebook集成到vsc中去 准备python / Anaconda :推荐专业的Python开发人员使用Anaconda进行包管理和版本控制安装jupyter notebook。pip3 install jupyter notebook 正常下载之后选择一个工作目录执行jupyt…

字节 GUI 代理模型 UI-TARS:具备像人一样的感知、推理、行动能力;SLAM-Omni:支持可控音色的语音对话模型

开发者朋友们大家好:这里是 「RTE 开发者日报」 ,每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE(Real-Time Engagement) 领域内「有话题的 新闻 」、「有态度的 观点 」、「有意思的 数据 」、「有思考的 文章 」、「有看点的 会议 」,但内容仅代表编辑…

htb Pandora walkthrough snmp + ssh 隧道 + 环境变量劫持

80端口web界面划到最下面发现两个邮箱 记录一下尝试加上域名dirsearch 扫描只扫到一个assets目录但是值得注意的是这个目录里面有个blog目录 也就意味着他的网站是有博客网站的 只是我们没扫描出来扫扫udp端口发现161 的 snmp是开着的用nmap 枚举进程 nmap -sU -p161 --script …

IT-Tools-开源好用的IT工具集

访问地址: 线上访问:https://it-tools.tech/ 开源社区:https://github.com/CorentinTh/it-tools 支持私有化部署: docker run -d --name it-tools --restart unless-stopped -p 8080:80 ghcr.io/corentinth/it-tools:latest简要介绍: IT-Tools 是为开发人员提供的在线工具…

4.4.2 版本更新来了!全新内容抢鲜看

产品更新概览 功能新增: 新版API支持在进行数据解析选择JSON节点时,全选下一层节点。 功能修复: 修复新版API认证功能; 修复报表后台系统用户权限问题。 功能优化: 支持在Linux X64版本上连接神通数据库; 支持连接2.x版本的TDengine数据库。 功能新增 在山海鲸可视化4.4.…

分享给大家一款网站四合一缩略图生成在线工具

​ 今天,我们要介绍一款能够极大提升网页设计和测试效率的工具——3M万能在线工具箱中的“四合一网页设备缩略图”生成工具。 为什么需要四合一缩略图生成在线工具? 在网页设计和开发过程中,确保网页在不同设备上(如台式机、笔记本电脑、平板电脑和手机)都能完美显示是至关…

ps ef命令查询进程号pid

ps ef命令查询进程号pid楼兰胡杨已经在《五分钟扫盲:25个工作中常用的Linux命令》分享了ps命令的简单使用方法,但是,写的过于笼统,这里详细介绍一下。语法:ps -ef | grep processName功能:查看当前进程 (process) 的状态。options:-e 显示所有进程,-f 全格式。说明:英…

详细剖析Java动态线程池的扩容以及缩容操作

前言 在项目中,我们经常会使用到线程来处理加快我们的任务。但为了节约资源,大多数程序员都会把线程进行池化,使用线程池来更好的支持我们的业务。 Java线程池ThreadPoolExecutor有几个比较核心的参数,如corePoolSize、maximumPoolSize等等。无论是在工作中还是在面试中,都…

Multiplayer Shooting Game

Launch game in settings添加多人游戏 设置游戏人数:选择网络模式:Play As Listen Server:其中一台有人游玩的机器充当服务器,需要图形渲染 Play As Client:指定一台机器作为服务器,没有人实际在这台机器上游玩游戏,无需图形渲染(大型多人游戏) 配置Project连接到Steam 启…

【Aegisub】卡拉OK模板执行器学习

目录什么是卡拉OK模板执行器卡拉OK模板执行流程概念解析template行code行code区模板修饰语声明类修饰语onceline [name]pre-line [name]sylfurisyl furi其他修饰语allcharfx namefxgroup namemultikeeptagsnoblanknotextrepeat n, loop n内联变量如何使用内联变量变量类型行(…