16-闭包的底层实现
16-闭包的底层实现以及调用楔子上一篇我们看了函数是如何调用的,这一次我们看一下函数中局部变量的访问、以及闭包相关的知识。
函数中局部变量的访问我们说过函数的参数和函数内部定义的变量都属于局部变量,所以它也一样是通过静态的方式进行访问。
123456789101112131415161718192021222324252627x = 123def foo(): global x a = 1 b = 2# a和b是局部变量,x是全局变量,因此是2print(foo.__code__.co_nlocals) # 2def bar(a, b): passprint(bar.__code__.co_nlocals) # 2def bar2(a, b): a = 1 b = 2 c = 3print(bar2.__code__.co_nlocals) # 3
因此我们看到,无论是参数还是内部新创建的变量,本质上都是局部变量。并且我们发现如果函数内部定义的变量如果和函数参数一致,那么参数就没用了,很好理解,因为本质上就相当于重新赋值罢了,此时外面无 ...
15-函数在底层是如何被调用的
15-函数在底层是如何被调用的楔子在上一篇博客中,我们说了Python函数的底层实现,并且还演示了如何自定义一个函数,虽然这在工作中没有太大意义,但是可以让我们深刻理解函数的行为。此外我们还介绍了如何获取函数的参数,而这一次我们就来看看函数如何调用的。
函数的调用1234567891011s = """def foo(): a, b = 1, 2 return a + bfoo()"""if __name__ == '__main__': import dis dis.dis(compile(s, "call_function", "exec"))
我们以一个非常简单的函数为例,看看它的字节码:
123456789101112131415161718192021 2 0 LOAD_CONST 0 (<code object foo at 0x00000219BA3F1450, file ...
14-函数在底层的数据结构以及创建方式
14-函数在底层的数据结构、以及它的创建方式楔子函数是任何一门编程语言都具备的基本元素,它可以将多个动作组合起来,一个函数代表了一系列的动作。当然我们之前说函数也是一个变量,该变量指向一个函数。而且在调用函数时会干什么来着,没错,要在运行时栈中创建栈帧,用于函数的执行。
那么下面就来看看函数在C中是如何实现的,生得一副什么模样。
PyFunctionObject对象我们说过Python中一切皆对象,函数也不例外。在Python中,函数这种抽象机制是通过PyFunctionObject对象实现的,位于 *Include/funcobject.h* 中。
12345678910111213141516171819202122typedef struct { PyObject_HEAD /* 头部信息, 不用多说 */ PyObject *func_code; /* 函数的PyCodeObject对象, 因为函数就是根据该PyCodeObject对象创建的 */ PyObject *func_globals; ...
13-剖析Python的流程控制语句
13-剖析Python的流程控制语句(if、for、while),以及异常捕获机制楔子在上一章中,我们介绍了Python虚拟机中常见的字节码指令。但我们的流程都是从上往下顺序执行的,在执行的过程中没有任何变化,但是显然这是不够的,因为怎么能没有流程控制呢。下面我们来看看Python所提供的流程控制手段,其中也包括异常检测机制。
Python虚拟机中的if控制流if字节码if语句算是最简单也是最常用的控制流语句,那么它的字节码是怎么样的呢?当然我们这里的if语句指的是if、elif、elif…、else整体,里面的if、某个elif或者else叫做该if语句的分支。
123456789101112131415s = """gender = "男"if gender == "男": print("nice muscle")elif gender == "女": print("白い肌")else: print("秀吉")&qu ...
12-剖析字节码指令
12-剖析字节码指令,从不一样的角度观测Python源代码的执行过程上一章中,我们通过_PyEval_EvalFrameDefault看到了Python虚拟机的整体框架,那么这一章我们将深入到_PyEval_EvalFrameDefault的各个细节当中,深入剖析Python的虚拟机,在本章中我们将剖析Python虚拟机是如何完成对一般表达式的执行的。这里的一般表达式包括最基本的对象创建语句、打印语句等等。至于if、while等表达式,我们将其归类于控制流语句,对于Python中控制流的剖析,我们将留到下一章。
简单回顾这里我们通过问与答的方式,简单回顾一下前面的内容。
请问 Python 程序是怎么运行的?是编译成机器码后在执行的吗?
不少初学者对 *Python* 存在误解,以为它是类似 *Shell* 的解释性脚本语言,其实并不是。虽然执行 *Python* 程序的 称为 *Python* 解释器,但它其实包含一个 “编译器” 和一个 “虚拟机”。
当我们在命令行敲下 python xxxx.py 时,*python* 解释器中的编译器首先登场,将 *P ...
11-探索Python虚拟机和字节码的奥秘
11-探索Python虚拟机执行字节码的奥秘这一次我们就来剖析Python运行字节码的原理,我们知道Python虚拟机是Python的核心,在源代码被编译成PyCodeObject对象时,就将由Python虚拟机接手整个工作。Python虚拟机会从PyCodeObject中读取字节码,并在当前的上下文中执行,直到所有的字节码都被执行完毕。
Python虚拟机的执行环境Python的虚拟机实际上是在模拟操作系统运行可执行文件的过程,我们先来看看在一台普通的x86的机器上,可执行文件是以什么方式运行的。在这里主要关注运行时栈的栈帧,如图所示:
x86体系处理器通过栈维护调用关系,每次函数调用时就在栈上分配一个帧用于保存调用上下文以及临时存储。CPU中有两个关键寄存器,rsp指向当前栈顶,rbp指向当前栈帧。每次调用函数时,调用者(Caller)负责准备参数、保存返回地址,并跳转到被调用函数中执行代码;作为被调用者(Callee),函数先将当前rbp寄存器压入栈(保存调用者栈帧位置),并将rbp设为当前栈顶(保存当前新栈帧的位置)。由此,rbp寄存器与每个栈帧中保存调用者栈帧地址一起完美地 ...
10-PyCodeObject对象和pyc文件
10-Python中的PyCodeObject对象与pyc文件楔子当我们想要执行一个py文件的时候,只需要python xxx.py即可,但是你有没有想过这背后的流程是怎么样的呢?从这里开始我们就开始进入到Python虚拟机的环节了,之前都是在介绍Python中的一些内置对象,不过虚拟机的执行流程、以及背后的原理却更是值得我们关注的。
这里我们先来说一下Python执行py文件的流程:
1. 首先将文件里面的内容读取出来, 所以从这个角度上讲, 文件名不一定非要是.py结尾, .txt也是可以的, 只要文件里面的内容符合Python代码规范即可
2. 读取文件里面的内容之后会对其进行分词, 将源代码切分成一个一个的token
3. 然后Python编译器会对token进行语法解析, 建立抽象语法树(AST, abstract syntax tree)
4. 编译器再将得到AST编译成字节码
5. 最终由Python虚拟机来执行字节码
首先我们从中看到了Python编译器、Python虚拟机,而且我们平常还会说Python解释器,那么三者之间有什么区别呢?
实际上Python解释 ...
09-字典和集合的底层实现
09-解密Python中字典和集合的底层实现,深度分析哈希表楔子Python的字典是一种映射型容器对象,保存了键(key)到值(value)的映射关系。通过字典,我们可以快速的实现值的查找,json这种数据结构也是借鉴了Python中的字典。而且字典在Python中是经过高度优化的,因为Python底层也在大量的使用字典这种数据结构。
那么这次我们就来全面分析一下Python中的字典。
基本使用我们先来回顾一下字典的基本使用,然后再来分析它的一些特性以及底层实现。
创建一个字典:
1234567891011121314151617181920212223242526272829303132# 创建一个字典d = {"a": 1, "b": 2}print(d) # {'a': 1, 'b': 2}# 或者我们还可以通过dict, 传入关键字参数即可d = dict(a=1, b=2, c=3, d=4)print(d) # {'a': ...
08-解密Python中列表的底层实现
08-解密Python中列表的底层实现楔子Python中的列表可以说使用的非常广泛了,在初学列表的时候,老师会告诉你列表就是一个大仓库,什么都可以存放。不过在最开始的几个章节中,我们花了很大的笔墨介绍了Python中的对象,并明白了Python中变量的本质,我们知道列表中存放的元素其实都是泛型指针PyObject *,所以到现在列表已经没有什么好神秘的了。
并且根据我们使用列表的经验,我们可以得出以下两个结论:
每个列表中的元素个数可以不一样:所以这是一个变长对象
可以对列表中的元素进行添加、删除、修改等操作,所以这是一个可变对象
在分析列表对应的底层结构之前,我们先来回顾一下列表的使用。
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455# 创建一个列表,这里是通过Python/C API创建的>>> lst = [1, 2, 3, 4]>>> lst[1, 2, 3, 4]# 往列表尾部追加一 ...
07-Python字符串的底层实现
07-Python字符串的底层实现楔子这一次我们分析一下Python中的字符串,首先Python中的字符串是一个变长对象,因为不同长度的字符串所占的内存空间是不一样的;但同时字符串又是一个不可变对象,因为一旦创建就不可以再修改了。
而Python中的字符串是通过unicode来表示的,因此在底层对应的结构体是PyUnicodeObject。但是为什么需要unicode呢?
首先计算机存储的基本单位是字节,由8个比特位组成,由于英文字母算上大小写只有52个,再加上若干字符,数量不会超过256个,因此一个字节完全可以表示,这些字符称之为ASCII字符。但是随着非英文字符的出现,导致一个字节已经无法表示了,只能曲线救国,对于一个字节无法表示的字符,使用多个字节表示。
但是这样会出现两个问题:
不支持多国语言,例如中文的编码不可以包含日文;
没有统一标准,例如中文有GB2312、GBK、GB18030等多个标准;
所以由于编码不统一,开发人员经常在不同的编码间来回转换,会错误频出。为了彻底解决这个问题,unicode标准诞生了。unicode对世界上的文字系统进行了系统的整理、编码,让计 ...