投稿007期|令人震惊到发指的PyObject对象代码设计之美
前言
最近在重温经典漫画《SlamDunk》的全国大赛篇,其中的一个情形可以很好的诠释虎躯一震这个状态——当樱木看到流川枫一次高难度投篮时内心的感受:“经过两万次射球练习后,樱木首次明白到流川枫这一球是相当厉害的,那正是他在两万次射球练习之中,经常在他脑海中出现的理想射球姿势”。


言归正传,其实对大多数程序开发人员来说,以上这个场景的感慨状态有时候也出现在我们看到经典代码的时候。最近正在思考关于Python语言的源生设计机制,有个问题不知道大家是否也有思考过:我们知道Python是由ANSI C实现的,在Python中一切都是对象的概念,但C并不是面向对象的语言,那么Python中的对象机制是如何实现的呢?带着这个疑问我研究了Python的源码,当我看到PyObject这个对象机制的核心结构体时我妥妥的震惊了,那么借着这期的主题就和大家分享一下PyObject对象基石的设计之美吧!
PyObject结构体介绍
通常来说,无论什么语言最终被计算机识别到的都是内存中的字节信息,那么对象实际上就是在更高的层次上把内存上的数据作为一个整体来考虑,这个整体可以是一个整数,可以是一个字符串,也就是我们所理解的对象。Python中所有的东西都是对象,它们拥有一些相同的内容,这些内容定义在PyObject这个结构体中,从Python源码文件object.h中可以找到它。
typedef struct _object {
PyObject_HEAD
} PyObject;
光看这个结构体可能还看不出什么高深的设计端倪,因为我们并不知道PyObject_HEAD是什么?源码中PyObject_HEAD是一个宏定义,定义了每一个Python对象所占内存的头部字节内容,那么我们把PyObject_HEAD这个宏定义替换成具体内容再直观的看下PyObject这个结构体。
typedef struct _object {
Py_ssize_t ob_refcnt;
struct _typeobject *ob_type;
} PyObject;
ob_refcnt是一个整形变量,它的作用是实现引用计数机制。比如一个对象A,当有一个新的PyObject 引用该对象时,A的引用计数增加;而当这个PyObject 被删除时,A的引用计数减少。当A的引用计数减少到0时,A就可以从堆上被删除,以释放出内存供别的对象使用。为什么是从堆上删除呢?因为Python中对象是在堆上申请的结构体,这点和C有很大的区别,C的变量是随函数创建,被压入栈中的。ob_type是一个指向_typeobject结构体的指针,这个结构体又是什么东西呢?实际上这个结构体也是一个对象,它是用来指定一个对象类型的类型对象,我们从源码中可以看出这个类型对象记录了不同的对象所需的内存空间的大小信息。那么简单的说,Python中对象机制的核心一个是引用计数,一个就是类型。
typedef struct _typeobject {
PyObject_VAR_HEAD
const char *tp_name; /* For printing, in format "<module>.<name>" */
Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */
/* Methods to implement standard operations */
destructor tp_dealloc;
printfunc tp_print;
getattrfunc tp_getattr;
setattrfunc tp_setattr;
cmpfunc tp_compare;
reprfunc tp_repr;
/* Method suites for standard classes */
PyNumberMethods *tp_as_number;
PySequenceMethods *tp_as_sequence;
PyMappingMethods *tp_as_mapping;
/* More standard operations (here for binary compatibility) */
hashfunc tp_hash;
ternaryfunc tp_call;
reprfunc tp_str;
getattrofunc tp_getattro;
setattrofunc tp_setattro;
/* Functions to access object as input/output buffer */
PyBufferProcs *tp_as_buffer;
/* Flags to define presence of optional/expanded features */
long tp_flags;
const char *tp_doc; /* Documentation string */
/* Assigned meaning in release 2.0 */
/* call function for all accessible objects */
traverseproc tp_traverse;
/* delete references to contained objects */
inquiry tp_clear;
/* Assigned meaning in release 2.1 */
/* rich comparisons */
richcmpfunc tp_richcompare;
/* weak reference enabler */
Py_ssize_t tp_weaklistoffset;
/* Added in release 2.2 */
/* Iterators */
getiterfunc tp_iter;
iternextfunc tp_iternext;
/* Attribute descriptor and subclassing stuff */
struct PyMethodDef *tp_methods;
struct PyMemberDef *tp_members;
struct PyGetSetDef *tp_getset;
struct _typeobject *tp_base;
PyObject *tp_dict;
descrgetfunc tp_descr_get;
descrsetfunc tp_descr_set;
Py_ssize_t tp_dictoffset;
initproc tp_init;
allocfunc tp_alloc;
newfunc tp_new;
freefunc tp_free; /* Low-level free-memory routine */
inquiry tp_is_gc; /* For PyObject_IS_GC */
PyObject *tp_bases;
PyObject *tp_mro; /* method resolution order */
PyObject *tp_cache;
PyObject *tp_subclasses;
PyObject *tp_weaklist;
destructor tp_del;
/* Type attribute cache version tag. Added in version 2.6 */
unsigned int tp_version_tag;
#ifdef COUNT_ALLOCS
/* these must be last and never explicitly initialized */
Py_ssize_t tp_allocs;
Py_ssize_t tp_frees;
Py_ssize_t tp_maxalloc;
struct _typeobject *tp_prev;
struct _typeobject *tp_next;
#endif
} PyTypeObject;
PyObject是一个定长对象的结构体,对于可变长度对象的结构体是PyVarObject,它比PyObject结构体多一个ob_size变量,用于指定容器中包含的元素数量。比如list中有5个元素,那么PyVarObject.ob_size的值就是5。PyVarObject实际上只是对PyObject的一个扩展而已,任何一个PyVarObject所占用的内存,开始部分的字节定义和PyObject是一样的。
动态类型的特性 厦门叉车哪家好 叉车价格是多少
我在《Python基础系列讲解—动态类型语言的特点》一文中讲到了Python作为动态类型语言的特点,变量不需要预先声明类型,当变量在赋值时解释器会根据值的类型创建对应的内存空间进行存储,并将变量指向这个地址空间。当时觉得这个机制太不可思议了,了解到PyObject这个结构体才发现原来是它的功劳。
比如Python创建一个整形对象PyIntObject,它会为这个对象分配内存,并进行初始化。然后这个对象会由一个PyObject变量来维护,因为每一个对象都拥有相同的对象头部,这使得对象的引用变得非常的统一。无论对象实际上的类型是什么,只需要通过PyObject指针就可以引用任意的一个对象。
总结
Python作为一门高级语言,其实大家也可以不必过多的推敲底层的实现机制,不过有一定程度的了解可以帮助我们更深入的理解和应用Python这门语言工具,甚至可以对解释器如何解释你的代码了如指掌,有助于排查语法层面的BUG。如果大家对Python底层的实现有浓厚的兴趣,这里推荐大家阅读《Python源码剖析》这本书。对了,我有电子版的,有需要的可以私信我。
投稿007期|令人震惊到发指的PyObject对象代码设计之美的更多相关文章
- 极客技术专题【007期】:jQuery初学者入门 - jQuery Event
日期:2013-8-19 来源:GBin1.com 技术专题介绍 专题:jQuery初学者入门[第三讲:jQuery Event] 分享人:极客标签技术编辑 -Lana (请站内关注分享人) 授课时 ...
- 安全意识第八期丨OMG!发个帖子竟然摊上大事了
互联网时代,话在网上说.钱在网上花.事在网上办,这早已成为一种习惯,越来越多的人也倾向于通过网络来获取信息. 借助现代信息技术,网络传播者通过即时通讯工具.微博.朋友圈等渠道发布信息,虽然传播起来更便 ...
- 《C专家编程》第四章——令人震惊的事实:数组和指针并不相同
数组和指针是C语言里相当重要的两部分内容,也是新手程序员最容易搞混的两个地方,本章我们锁定指针与数组,探讨它们的异同点. 首先来看指针与数组在声明上的区别: int a[10]; int *p; 很明 ...
- web 9个令人震惊的WebGL示例
20个使用WebGL和Three.js实现的网页场景 https://www.open-open.com/news/view/9d8136 20个使用WebGL和Three.js实现的网页场景 htt ...
- 2015年传智播客JavaEE 第168期就业班视频教程day45-ERP项目-01 10-类图结构分析设计
运行astah-pro.bat,这是windows下运行的.astah-run.sh是Linux下运行的. 类结构视图的作用是描述类模型和模型与模型之间的关系,也就是说我们在这要把这个一对多和多对多的 ...
- 发短信的主要代码(SmsManger)
SmsManager smsManager=SmsManager.getDefault(); smsManager.sendTextMessage(number,null,sms, null,null ...
- 剑指offer-高质量的代码
小结: 规范性:书写清晰.布局清晰.命名合理 完整性:完成基本功能.考虑边界条件.做好错误处理 鲁棒性:采取防御性编程.处理无效输入 面试这需要关注 输入参数的检查 错误处理和异常的方式(3种) 命名 ...
- AngularJS 指循环数组对象
<!DOCTYPE html><html><head><meta http-equiv="Content-Type" content=&q ...
- 浅谈测试驱动开发(TDD)
测试驱动开发(TDD)是极限编程的重要特点,它以不断的测试推动代码的开发,既简化了代码,又保证了软件质量.本文从开发人员使用的角度,介绍了 TDD 优势.原理.过程.原则.测试技术.Tips 等方面. ...
随机推荐
- Docker+Kubernetes(k8s)微服务容器化实践
第1章 初识微服务微服务的入门,我们从传统的单体架构入手,看看在什么样的环境和需求下一步步走到微服务的,然后再具体了解一下什么才是微服务,让大家对微服务的概念有深入的理解.然后我们一起画一个微服务的架 ...
- [转]SVN服务器搭建和使用(二)
上一篇介绍了VisualSVN Server和TortoiseSVN的下载,安装,汉化.这篇介绍一下如何使用VisualSVN Server建立版本库,以及TortoiseSVN的使用. 首先打开Vi ...
- python HtmlTestRunner python2.x python3.x报告优化模板源码下载
文件py压缩包下载地址https://files.cnblogs.com/files/SunshineKimi/HtmlTestRunner2.x_3%2Cx.rar 源码拷贝如下: python 2 ...
- Kafka设计解析(五)Kafka性能测试方法及Benchmark报告
转载自 技术世界,原文链接 Kafka设计解析(五)- Kafka性能测试方法及Benchmark报告 摘要 本文主要介绍了如何利用Kafka自带的性能测试脚本及Kafka Manager测试Kafk ...
- JSONP - 从理论到实践
同源策略 ajax之所以需要“跨域”,罪魁祸首就是浏览器的同源策略.即,一个页面的ajax只能获取这个页面相同源或者相同域的数据. 如何叫“同源”或者“同域”呢?——协议.域名.端口号都必须相同.例如 ...
- Jenkins+github的一次定时构建示例
首先说明,我的电脑环境是windows,所以以下的示例是基于windows10 X64. 一.新建任务,填写名称,选择类型,点击左下角的[确定] 二.配置 1.General 2.源码管理 之前在gi ...
- 1350: To Add Which? (优先队列+贪心 或者 数组模拟)
1350: To Add Which? Submit Page Summary Time Limit: 1 Sec Memory Limit: 128 Mb Submitt ...
- Tensorflow-slim 学习笔记(二)第一层目录代码解读
通过阅读代码来学习,一向时最直接快速的.本章将讲解slim的第一层目录tensorflow/tensorflow/contrib/slim/python/slim的代码. 本层代码主要包括learni ...
- 12 Bit ADC与LSB的含义
[转]12 Bit ADC与LSB的含义 LSB(Least Significant Bit),意为最低有效位:MSB(Most Significant Bit),意为最高有效位,若MSB=1,则表示 ...
- Linux虚拟机下安装Oracle 11G教程
1.安装环境 操作系统:Red hat 6.5 内存:内存最低要求256M (使用:grep MemTotal /proc/meminfo 命令查看) 交换空间:SWAP交换空间大小根据内存大小决定( ...