最新要闻
- 环球信息:让老人暖胃更暖心
- 天天即时:两倍性能提升!玩家推测PS5 Pro详细配置
- 史上第一次 美国要强制规定航空公司取消或者延误时现金赔偿旅客
- 印度一只老虎杀死花豹 但却不吃肉:原因有二
- 建议买SD卡!《塞尔达传说:王国之泪》容量达16.3GB
- 环球消息!拒绝流氓应用 Win11安卓子系统重磅升级 安全自有一套
- 怀卡托大学学费_怀卡托大学-全球新资讯
- 茂名西站_关于茂名西站的简介
- 冬至是国家法定假期吗_冬至是国家法定假日-每日焦点
- 土豪该有的OLED电竞装备 AOC 2K240Hz显示器6949元(0.01ms响应)
- 情侣吵架从8楼将萨摩耶扔下 后续来了:男主高空抛物被抓
- 女子车位被女邻居霸停数月:一怒之下 直接焊死_全球今日讯
- 全球快资讯丨古力娜扎COS《王者荣耀》貂蝉 美丽动人超越原作
- 传音控股:数字人系统顺利通过全部48个测试项|天天播报
- 【天天播资讯】《羊了个羊》被通报:涉欺骗误导强迫用户!曾被吐槽广告多
- 哈尔滨拆承重墙高楼现新裂缝 专家:或能加固到原状态 成本较高
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
深入理解 python 虚拟机:描述器实现原理与源码分析
深入理解 python 虚拟机:描述器实现原理与源码分析
在本篇文章当中主要给大家介绍描述器背后的实现原理,通过分析 cpython 对应的源代码了解与描述器相关的字节码的指令,我们就可以真正了解到描述器背后的原理!
从字节码角度看描述器
在前面的内容当中我们已经详细分析了描述器的使用和其相关的应用,我们通常使用描述器都是将其作为类的一个类属性使用,而使用的方式就是 a.attr
,而这个使用方式使用的字节码如下所示:
Python 3.10.9 (main, Jan 11 2023, 09:18:18) [Clang 14.0.6 ] on darwinType "help", "copyright", "credits" or "license" for more information.>>> import dis>>> dis.dis("a.attr") 1 0 LOAD_NAME 0 (a) 2 LOAD_ATTR 1 (attr) 4 RETURN_VALUE>>>
可以看到的是真正调用的字节码是 LOAD_ATTR
,因此只需要我们深入 LOAD_ATTR
指令我们就能够了解这其中所有发生的内容,了解魔法背后的神秘。
(资料图)
描述器源码分析
cpython 虚拟机当中执行这个字节码的内容如下:
TARGET(LOAD_ATTR) { PyObject *name = GETITEM(names, oparg); PyObject *owner = TOP(); PyObject *res = PyObject_GetAttr(owner, name); Py_DECREF(owner); SET_TOP(res); if (res == NULL) goto error; DISPATCH();}
owner
对应上面的代码当中的 a
对象,name
对应上面的字符串 attr
。从上面的代码分析我们可以知道真正获取属性的函数为 PyObject_GetAttr
,这个函数的源程序如下所示:
PyObject *PyObject_GetAttr(PyObject *v, PyObject *name){ // 首先获取对象 v 的类型 ,对应上面的代码的话就是找到对象 a 的类型 PyTypeObject *tp = Py_TYPE(v); if (!PyUnicode_Check(name)) { PyErr_Format(PyExc_TypeError, "attribute name must be string, not "%.200s"", name->ob_type->tp_name); return NULL; } // 获取对象的 tp_getattro 函数 这个函数就是负责属性查找的函数 我们一般使用的这个属性查找函数都是 // object 这个基类的属性查找函数 if (tp->tp_getattro != NULL) return (*tp->tp_getattro)(v, name); if (tp->tp_getattr != NULL) { const char *name_str = PyUnicode_AsUTF8(name); if (name_str == NULL) return NULL; return (*tp->tp_getattr)(v, (char *)name_str); } PyErr_Format(PyExc_AttributeError, ""%.50s" object has no attribute "%U"", tp->tp_name, name); return NULL;}
在上面的代码当中我们提到了 object 这个基类,因为我们需要找到他的属性查找函数,因此我们看一下这个基类在 cpython 内部的定义,在 cpython 内部 object 基类定义为 PyBaseObject_Type
:
PyTypeObject PyBaseObject_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "object", /* tp_name */ sizeof(PyObject), /* tp_basicsize */ 0, /* tp_itemsize */ object_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_reserved */ object_repr, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ (hashfunc)_Py_HashPointer, /* tp_hash */ 0, /* tp_call */ object_str, /* tp_str */ // 这个就是真正的属性查找函数 PyObject_GenericGetAttr, /* tp_getattro */ PyObject_GenericSetAttr, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ PyDoc_STR("object()\n--\n\nThe most base type"), /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ object_richcompare, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ object_methods, /* tp_methods */ 0, /* tp_members */ object_getsets, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ object_init, /* tp_init */ PyType_GenericAlloc, /* tp_alloc */ object_new, /* tp_new */ PyObject_Del, /* tp_free */};// 从上面的 object 定义可以看到真正的查找函数为 PyObject_GenericGetAttr 其函数内容如下所示:PyObject *PyObject_GenericGetAttr(PyObject *obj, PyObject *name){ return _PyObject_GenericGetAttrWithDict(obj, name, NULL, 0);}
_PyObject_GenericGetAttrWithDict
函数定义如下所示:
/* Generic GetAttr functions - put these in your tp_[gs]etattro slot. */PyObject *_PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, PyObject *dict, int suppress){ /* Make sure the logic of _PyObject_GetMethod is in sync with this method. When suppress=1, this function suppress AttributeError. */ // 首先获取对象的类型 针对于上面的源代码来说就是找到对象 a 的类型 PyTypeObject *tp = Py_TYPE(obj); PyObject *descr = NULL; PyObject *res = NULL; descrgetfunc f; Py_ssize_t dictoffset; PyObject **dictptr; if (!PyUnicode_Check(name)){ PyErr_Format(PyExc_TypeError, "attribute name must be string, not "%.200s"", name->ob_type->tp_name); return NULL; } Py_INCREF(name); if (tp->tp_dict == NULL) { if (PyType_Ready(tp) < 0) goto done; } // 这个是从所有的基类当中找到一个名字为 name 的对象 如果没有就返回 NULL // 这里的过程还是比较复杂 需要从类的 mro 序列当中进行查找 descr = _PyType_Lookup(tp, name); f = NULL; // 如果找到的类对象不为空 也就是在类本身或者基类当中找到一个名为 name 的对象 if (descr != NULL) { Py_INCREF(descr); // 得到类对象的 __get__ 函数 f = descr->ob_type->tp_descr_get; // 如果对象有 __get__ 函数则进行进一步判断 if (f != NULL && PyDescr_IsData(descr)) { // PyDescr_IsData(descr) 这个宏是查看对象是否有 __set__ 函数 // 如果是类对象又有 __get__ 函数 又有 __set__ 函数 则直接调用对象的 __get__ 函数 并且将结果返回 // 这里需要注意一下优先级 这个优先级是最高的 如果一个类对象定义了 __set__ 和 __get__ 函数,那么 // 就会直接调用类对象的 __get__ 函数并且将这个函数的返回值返回 res = f(descr, obj, (PyObject *)obj->ob_type); if (res == NULL && suppress && PyErr_ExceptionMatches(PyExc_AttributeError)) { PyErr_Clear(); } goto done; } } // 如果没有名为 name 的类对象 或者虽然有名为 name 的对象 但是只要没有同时定义 __get__ 和 __set__ 函数就需要 // 继续往下执行 从对象本省的 dict 当中寻找 if (dict == NULL) { /* Inline _PyObject_GetDictPtr */ // 这部分代码就是从对象 obj 当中找到对象的 __dict__ 字段 dictoffset = tp->tp_dictoffset; if (dictoffset != 0) { if (dictoffset < 0) { Py_ssize_t tsize; size_t size; tsize = ((PyVarObject *)obj)->ob_size; if (tsize < 0) tsize = -tsize; size = _PyObject_VAR_SIZE(tp, tsize); assert(size <= PY_SSIZE_T_MAX); dictoffset += (Py_ssize_t)size; assert(dictoffset > 0); assert(dictoffset % SIZEOF_VOID_P == 0); } dictptr = (PyObject **) ((char *)obj + dictoffset); dict = *dictptr; } } // 如果对象 obj 存在 __dict__ 字段 那么就返回 __dict__ 字段当中名字等于 name 的对象 if (dict != NULL) { Py_INCREF(dict); res = PyDict_GetItem(dict, name); if (res != NULL) { Py_INCREF(res); Py_DECREF(dict); goto done; } Py_DECREF(dict); } // 如果类对象定义了 __get__ 函数没有定义 __set__ 函数而且在 dict 当中没有找到名为 name 的对象的话 // 那么久调用类对象的 __get__ 函数 if (f != NULL) { res = f(descr, obj, (PyObject *)Py_TYPE(obj)); if (res == NULL && suppress && PyErr_ExceptionMatches(PyExc_AttributeError)) { PyErr_Clear(); } goto done; } // 如果类对象没有定义 __get__ 函数那么就直接将这个类对象返回 if (descr != NULL) { res = descr; descr = NULL; goto done; } if (!suppress) { PyErr_Format(PyExc_AttributeError, ""%.50s" object has no attribute "%U"", tp->tp_name, name); } done: Py_XDECREF(descr); Py_DECREF(name); return res;}
根据对上面的程序进行分析,我们可以到得到从对象当中获取属性的顺序和优先级如下所示(以 a.attr
为例子):
- 如果属性不是类属性,那么很简单就是直接从对象本身的
__dict__
当中获取这个对象。 - 如果属性是类属性,如果同时定义了
__get__
和__set__
函数,那么就会调用这个类对象的__get__
函数,将这个函数的返回值作为a.attr
的返回值。 - 如果属性是类属性,如果只定义了
__get__
函数,那么就会从对象a
本身的__dict__
当中获取attr
,如果attr
存在与a.__dict__
当中,那么久返回这个结果,如果不存在的话那么就会调用__get__
函数,将这个函数的返回值作为a.attr
的结果,如果连__get__
都没有定义,那么就会直接返回这个类对象。
上面的函数过程用 python 语言来描述的话如下所示:
def find_name_in_mro(cls, name, default): "Emulate _PyType_Lookup() in Objects/typeobject.c" for base in cls.__mro__: if name in vars(base): return vars(base)[name] return defaultdef object_getattribute(obj, name): "Emulate PyObject_GenericGetAttr() in Objects/object.c" null = object() objtype = type(obj) cls_var = find_name_in_mro(objtype, name, null) descr_get = getattr(type(cls_var), "__get__", null) if descr_get is not null: if (hasattr(type(cls_var), "__set__") or hasattr(type(cls_var), "__delete__")): return descr_get(cls_var, obj, objtype) # data descriptor if hasattr(obj, "__dict__") and name in vars(obj): return vars(obj)[name] # instance variable if descr_get is not null: return descr_get(cls_var, obj, objtype) # non-data descriptor if cls_var is not null: return cls_var # class variable raise AttributeError(name)
仔细分析上面的 python 代码,他的整个逻辑和我们前面分析的 c 代码的逻辑是一样的。首先是获取对象的类型,然后从类型当中获取名字为 name 的属性,如果类属性定义了 __get__
函数,则需要进行描述器的判断,否则直接从对象的 __dict__
当中获取,如果其中没有则返回类对象。
总结
在本篇文章当中主要给大家深入分析了在 cpython 的内部对于描述器的实现原理,其中最重要的就是在获取属性的时候的优先级了。我们直接从 c 代码的层面分析了整个获取属性的优先级,并且给出了 python 层面的代码帮助大家理解。
本篇文章是深入理解 python 虚拟机系列文章之一,文章地址:https://github.com/Chang-LeHung/dive-into-cpython
更多精彩内容合集可访问项目:https://github.com/Chang-LeHung/CSCore
关注公众号:一无是处的研究僧,了解更多计算机(Java、Python、计算机系统基础、算法与数据结构)知识。
关键词:
深入理解 python 虚拟机:描述器实现原理与源码分析
88.函数指针
环球信息:让老人暖胃更暖心
天天即时:两倍性能提升!玩家推测PS5 Pro详细配置
史上第一次 美国要强制规定航空公司取消或者延误时现金赔偿旅客
印度一只老虎杀死花豹 但却不吃肉:原因有二
建议买SD卡!《塞尔达传说:王国之泪》容量达16.3GB
环球消息!拒绝流氓应用 Win11安卓子系统重磅升级 安全自有一套
怀卡托大学学费_怀卡托大学-全球新资讯
茂名西站_关于茂名西站的简介
冬至是国家法定假期吗_冬至是国家法定假日-每日焦点
网络基础 登录对接CAS-跨域导致的一个意想不到的Bug
土豪该有的OLED电竞装备 AOC 2K240Hz显示器6949元(0.01ms响应)
情侣吵架从8楼将萨摩耶扔下 后续来了:男主高空抛物被抓
女子车位被女邻居霸停数月:一怒之下 直接焊死_全球今日讯
全球快资讯丨古力娜扎COS《王者荣耀》貂蝉 美丽动人超越原作
传音控股:数字人系统顺利通过全部48个测试项|天天播报
谈一谈如何使用etcd中的事务以及自己的理解
Ansible快速入门(下)
@RequestParam注解参数-世界热讯
【天天播资讯】《羊了个羊》被通报:涉欺骗误导强迫用户!曾被吐槽广告多
哈尔滨拆承重墙高楼现新裂缝 专家:或能加固到原状态 成本较高
谢谢你们,让我们的延吉之行更有意义|全球速读
87.特殊用途语言特性
20天学会 java
讯息:发挥工业互联网平台立体化赋能作用!山东“数字强省”再加速
马斯克曾发出人口消失警告 日本912万65岁以上老人仍在工作 热推荐
RTX 2080S实机运行《GTA6》画面曝光: 熟悉的味道
天天头条:线下大牌:CariaKnar内衣旗舰店男士6条装内裤39.9元
搭载麒麟710A!华为nova 8 SE官方二手机上架:999元
耗时5个月:中国空间站有了新发现 当前讯息
使用 HTTP/2 加速 Node.js 应用
世界要闻:MockMVC的使用
世界头条:辽宁:延续实施社会保险费惠企减负政策
天天热推荐:“最强法务部”出手维权:安卓Switch模拟器Skyline宣布停止开发
索尼PS5 Slim/Pro详细配置曝光:性能翻番了!
特斯拉率先涨价 电动车价格战没戏了?“白色石油”碳酸锂价格重回20万/吨以上
深蹲后开始起跳!长城汽车4月销量超9.3万辆 同比大涨73% 全球视讯
担心的事情发生了!男子庆祝离婚去蹦极绳子断裂:脖子腰椎全摔断_全球快播
度小满发布2022ESG报告:践行ESG发展理念助推可持续发展_视点
vue中手动清除KeepAlive缓存|环球视讯
【世界快播报】钉钉PC端使用 Blazor WebAssembly 读取用户信息
环球视点!财报解析 | 2022年上汽扣非净利跌破百亿 上汽乘用车盈利仍承压
西藏航空回应客机机舱内出现浓烟返航:空调组件故障 安全没影响
全球速看:21金维他维生素C片60粒9.9元抄底:立减50元
杰克辣条再开直播虐猫?人民网怒批:处刑式虐猫可憎 向人类良知挑战
砸掉承重墙 全楼无家可归 损失1.6亿!最多可判刑7年
【世界聚看点】印度:所有智能手机必须标配FM收音机 默认开启
蕙兰怎么养才好_蕙兰怎么养
全球球精选!记录--极致舒适的Vue页面保活方案
世界热头条丨云图说|图解制品仓库CodeArts Artifact
国家医保局:一季度基本医疗保险基金整体运行平稳
iPhone 15 Pro Max影像升级巨大!苹果这次硬刚安卓_动态焦点
观察:显卡散热疯了!背板上装风扇、热管:实测根本没用
买特斯拉等车更省了 上海:6月30日前购买纯电动车补贴1万
中国央行连续6个月增持黄金:这是啥意思? 新动态
当前速读:女子霸占车位拒绝挪车 业主怒将车位焊上 律师:虽有不当但不违法
世界最新:肃南:按下项目建设“快进键”
读书笔记丨理解和学习事务,让你更好地融入云原生时代
第二章-Java程序的设计环境
Python第三方库安装教程、什么是第三方库_全球消息
AI来势汹汹,这份「生存计划」请查收!|世界实时
商品日报(5月8日):商品市场情绪回暖 双焦大涨超6%豆一涨超5%
《护心》雁回人物解析 《护心》雁回是好人还是坏人_当前通讯
每日消息!阿汤哥开战机为英国王送祝福:你可以当我的僚机
淘宝发布“时光机”:可查看20年消费数据 你一共花了多少钱?
赔钱卖车?福特电动车业务巨亏 卖一辆车亏40万元 微头条
摆脱依赖美国GPS!日本计划大幅强化自家准天顶导航系统:卫星从4颗增至11颗
拉萨一路口红绿灯只有3秒?“飙车”才能过 已延长至15秒
嫁入高门的女人百度云 嫁入高门的女人-天天快资讯
昇腾实战丨DVPP媒体数据处理视频解码问题案例
记一次springboot项目漏洞挖掘 全球观速讯
微资讯!科创板收盘播报:科创50指数涨0.77% 软件股显著回暖
奇瑞QQ冰淇淋的智能化驾驶 可坡道起步、动力随心
【环球聚看点】马斯克否认家里有矿:不是富二代而是白手起家
挑战千元旗舰耳机!真我Buds Air5 Pro行业首发50dB降噪
怎么用手机更健康?专家:正常光照下建议亮度控制在50%、距离50cm 天天亮点
巴菲特将AI比作原子弹:将会改变一切
网友的iPhone 14 Pro Max烧屏!苹果售后反馈“屏幕没问题”|动态焦点
客所思s10 客所思控制面板下载s10 环球最资讯
天天观点:Linux基础18 磁盘介绍, 结构, 磁盘分区Fdisk
Marked.js让您的文档编辑更加轻松自如-环球报资讯
Kerberos协议原理 全球时讯
验证码短信 API 接入指南:Java 语言示例代码 天天新消息
Tcl/Tk教程_编程入门自学教程_菜鸟教程-免费教程分享_世界观速讯
iPhone 16 Pro将采用固态按键:还有屏下Face ID!|世界今头条
咋想的?一住户小区花园内放生蟑螂 专家:病菌宿主可传播疾病_天天通讯
即时:餐馆有机花菜无认证遭索赔500元 正当维权还是恶意索赔?
全国首例超长矸石充填开采工作面在山能鲁西矿业新巨龙公司“上线”
关于Kubernetes-v1.23.6-master节点的初始化操作|全球播报
WEB中间件常见漏洞总结 当前通讯
企业短信遭疯狂盗用,可能是没配置验证码_当前报道
[webrtc 入门系列] centos搭建coturn服务器 当前播报
欧普康视:目前OK镜没有涉及到AIGC技术_今日关注
《小美人鱼》正片片段曝光:黑小美人鱼海底一展歌喉 天天滚动
全球今日讯!漫威影史最高分电影 《银河护卫队3》票房破2亿
俞敏洪谈为什么大量孩子失去好奇心:中国填鸭式教育、老师引导等造成 每日热点
世界消息!kingbase之ksql命令工具
【Issues】axios如何获取responseType为blob的请求的错误信息 环球时讯
浙江风彩网福彩双色球走势图_浙江风彩网 环球资讯