Python的加载过程与内存模型(CPython实现)
Python的加载过程

编译时
Python 与其他语言一样也具有编译过程 ,将Python文件转换为字节码文件供解释器进行下一步的处理。进行语义分析,语法分析,具象生成树,抽象生成树,最终将文件转换为字节码。
对对象的预处理:
对于左值(变量名)会在编译期被加入名称表(co_names
),并加入到 一个名称表元组中。
对于右侧创建的变量,根据类型的不同,加入到常量表中。
将全局变量替换为名称表中的索引,而索引映射了对应的常量对象指针
在python文件中的变量声明”a = 3” 变成了 “名字表中‘a’的内存地址 指向了常量表中‘3’的内存地址”
为了操作更加方便灵活,类定义中的类变量则直接被装载到类的命名空间进行管理。
将局部变量替换为局部变量表+插槽
运行时
将python字节码文件加载到虚拟机。不同的python组件会执行不同的装载行为。最终将对象加载到命名空间,进行执行。
组件加载过程
Python的加载逻辑是将文件中定义的内容 转换为 各个层级维护的命名空间中的映射关系。进行调用时 通过查询命名空间,找到对应的待执行内容(类,方法,变量),执行。
组件和类的加载:
1 2 3 4
| class A: x = 10 def __init__(self, val): self.val = val
|
等价于 :
1 2 3
| namespace = {} exec(code_object_for_class_A, globals(), namespace) A = type("A", (object,), namespace)
|
组件和类的加载过程:
定义一个了命名空间(内存申请)
使用code_object 加载类A中的元素定义到命名空间
再在外层将类与该命名空间绑定(实际上也将A添加到外部的命名空间)
cpython 使用code_object这个对象实现了向类中添加其所属元素的加载过程
函数/方法
方法中同样会创建一个code object 对象,但是不会将变量-引用转换成k-v模式加入到函数的命名空间,而是作为字节码存储在命名空间中,当方法被执行时,函数会调用字节码文件执行,创建一系列的参数元组,然后通过索引→key→value 的调用方式查询待处理的对象
实例的创建
在python中创建一个实例
1 2 3 4 5 6 7 8 9 10
| class person : def __init__(self,name): self.name = name nobody = person("nobody")
|
函数的执行
在python中调用一个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| def say_hello(name): print(locals()) print("hello ", name) if __name__ == "__main__": print(locals()) print("variable in func say_hello "+say_hello.__code__.co_varnames[0]) say_hello("codfish")
|
方法的执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| class someone : print(locals()) def __init__(self,name): self.name=name print(locals()) def say_hello(self): print(self.__dict__) print(locals()) print("hello ", self.name) print(locals())
if __name__ == "__main__": print(locals()) print("variable in func say_hello "+someone.say_hello.__code__.co_varnames[0]) print("someone's namespace:",someone.__dict__) someone('codfish').say_hello()
|
命名空间的查询
1 2 3 4
| locals() globals() __dict__ dir()
|
多态
python是通过命名空间来管理每个对象的访问权限的。
所以调用都遵循着 当调用函数,类时,查询当前层的命名空间,检索对应的函数名,类名信息。找到对应的函数,类的位置,进行执行。
那么我们可以看看python是如何实现多态的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| class anmial :
def __init__(self,name): self.name = name def eat(self): print(self.name + " eat nothing, still hungry")
class duck(anmial): def __init__(self,name="duck"): self.name = name
class cat(anmial) :
def __init__(self,name="cat"): self.name = name def eat(self): print(self.name + " eat some fish")
class dog(anmial):
def __init__(self,name="dog"): self.name = name def eat(self): print(self.name + " eat some bones")
class ghost :
def __init__(self,name="ghost") : self.name = name def eat(self): print(self.name+ " eat some blood")
class person :
def __init__(self,name="person") : self.name = name def want_eat(self): print(self.name+ " eat some noodls")
def toldAnmialToEat(anmial):
print(locals()) anmial.eat()
toldAnmialToEat(duck()) print(dir(duck)) toldAnmialToEat(cat()) toldAnmialToEat(dog()) toldAnmialToEat(ghost()) toldAnmialToEat(person())
|
可以看出
1.子类会从父类那里继承它没有重新的方法。
2.当子类重写了方法后,会执行子类的方法
非子类但具有相同方法的类也可以正确执行,说明函数的定义只是声明式约束,不会进行类的检查
当传入的类不具备方法时,会报错。由3,4说明还是根据命名空间中的功能列表来进行的函数执行
变量,引用,类型和对象
通过分析,我们可以看到Python中的所有对象都是由命名空间来组织和维护的。
变量 : python中的变量名在字节码中是以名称索引的形式出现,并存于名称表中。
引用 : 引用在python中表现为一种“间接”的引用,它不是通过标识符→实际对象的地址指针→获取实际的对象。而是通过创建了标识符对象→标识符加入name表→实际对象加入常量表→通过命名空间完成映射绑定。再通过命名空间中的k-v关系查询到实际被调用的对象。
类型 : Python是弱类型语言的正是基于这种引用的实现来构建的。虽然在底层Python的对象依然是存在类型的,但键与值之间的关系是基于映射关系,而不是指针。由于键值映射的动态性,类型检查在运行时进行,名称本身不携带类型信息
对象 :同样是因为这种引用的构建,Python中的一切皆对象实施的更加彻底——- 变量名也是作为对象存储的。这样的设计让我们不必深入底层才能获取到类,函数的创建信息,只需要找到其中的映射关系,就能对类或者函数中的属性进行操作修改。