内核达人

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 4|回复: 0

python类在虚拟机中长啥样

[复制链接]

25

主题

25

帖子

83

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
83
发表于 7 天前 | 显示全部楼层 |阅读模式
哈喽,大家好,我就是那个不喜欢在大厂搬砖,不喜欢在研究院做研究,只喜欢创业做计算机底层课程的coder,子牙老师

写篇文章与大家分享一下Python虚拟机CPython实现面向对象机制的细节

Python是编译型语言,coder编写的Python代码,编译生成的是Python虚拟机CPython才能够识别的字节码,而不是操作系统上能够直接运行的机器码

比如coder写一个Python类
class Stu:    pass

生成的字节码长这样
那你有没有想过:CPython执行完这些字节码指令,类Stu在虚拟机内部长啥样?

如果你写过这样的Python代码
class Stu:    passprint(type(Stu))
你会发现它的输出结果是
  1. <class 'type'>
复制代码

这个type就是类Stu在虚拟机CPython中的样子,对应的C语言结构体是PyHeapTypeObject
从Python中的class Stu,到C语言中的PyHeapTypeObject,中间的过程是怎样的呢?言外之意就是,任何一门编程语言,它的类是如何转变成C语言中的struct的呢

以下,enjoy

字节码指令LOAD_BUILD_CLASS
当CPython的执行引擎执行到指令LOAD_BUILD_CLASS时,会调用到函数builtin___build_class__

builtin___build_class__(……){    ……    meta = (PyObject *) (&PyType_Type);    ……}

PyType_Type是什么呢?是所有Python 类型(类对象)的类型对象(metaclass),你可以理解成是所有类对象的基类
PyType_Type有几个重要的函数,在创建Python类对应的类对象时,都会用上
比如type_call,每个Python类对应的类对象,就是在这个函数中创建的

随着代码往后面走,会进入函数_PyObject_MakeTpCall

_PyObject_MakeTpCall(……) {    ……    ternaryfunc call = Py_TYPE(callable)->tp_call;    result = call(callable, argstuple, kwdict);    ……}

代码进入PyType_Type的type_call

创建PyHeapTypeObject
type_call的核心逻辑
type_call(……) {    ……    obj = type->tp_new(type, args, kwds);    ……    int res = type->tp_init(obj, args, kwds);}
type_new的核心逻辑
type_new(……) {    ……    type = (PyTypeObject *)metatype->tp_alloc(metatype, nslots);    et = (PyHeapTypeObject *)type;}
这里面这个tp_alloc,PyType_Type中初始赋值是0,后面会被赋值为函数PyType_GenericAlloc。论证一下
执行完type_call,Python类对应的类对象就创建完成

这个流程研究完我就想:一、创建出来的类对象PyHeapTypeObject存储在哪?二、创建出来的类对象PyHeapTypeObject与GC之间的关系是如何建立的

存储在哪?
在CPython眼中,一个.py文件是一个模块,对应的结构体是
每个模块对象(PyModuleObject)都关联一个 dict,这个 dict 是变量作用域字典,用于存储模块的所有全局变量,也就是 Python 中的 globals()。除了 globals,执行过程中还有 locals(局部变量表)和 builtins(内建作用域)。

当我们定义一个类时,例如 class Stu:,Python 实际创建了一个 PyHeapTypeObject* 作为类对象,并将它以 {"Stu": } 的形式插入模块的 dict 中,从而在全局作用域中完成类名与类型对象的绑定。
PyHeapTypeObject也是一个Object,就会参与GC,那PyHeapTypeObject与GC是如果关联起来的呢?

与GC的关系
CPython运行起来,会创建一个运行时环境
其中generations就是分代收集器,创建的Object挂到generations的链表中,就会被扫描到。链表就是PyGC_Head
内存回收时,如果ob_refcnt=0,就会被回收。那PyHeapTypeObject与GC的关系是如何建立的呢?答案就在函数PyType_GenericAlloc
PyType_GenericAlloc(……) {    ……    if (_PyType_IS_GC(type)) {        obj = _PyObject_GC_Malloc(size);    }else {        obj = (PyObject *)PyObject_MALLOC(size);    }    if (_PyType_IS_GC(type)) {        _PyObject_GC_TRACK(obj);    }}
_PyObject_GC_Malloc与PyObject_MALLOC的区别是,_PyObject_GC_Malloc申请内存时会多申请一个PyGC_Head,挂到垃圾收集器中需要
真正挂到垃圾收集器中是_PyObject_GC_TRACK
#define _PyObject_GC_TRACK(op)     _PyObject_GC_TRACK_impl(__FILE__, __LINE__, _PyObject_CAST(op))_PyObject_GC_TRACK_impl(……) {    ……    PyGC_Head *generation0 = tstate->interp->gc.generation0;    PyGC_Head *last = (PyGC_Head*)(generation0->_gc_prev);    _PyGCHead_SET_NEXT(last, gc);    _PyGCHead_SET_PREV(gc, last);    _PyGCHead_SET_NEXT(gc, generation0);    generation0->_gc_prev = (uintptr_t)gc;    ……}

至此,Python虚拟机CPython为一个用户自定义的Python类创建PyHeapTypeObject全部完成

创建对象
基于用户自定义的Python类创建对象,比如
class Stu:    passstu = Stu()

代码流程:
Stu PyHeapTypeObject.tp_call    Stu PyHeapTypeObject.tp_new        Stu PyHeapTypeObject.tp_alloc            PyType_GenericAlloc
一个Python对象就是这样创建出来的~



本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|内核达人

GMT+8, 2025-12-6 12:36 , Processed in 0.051967 second(s), 19 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表