前言

最近在重温经典漫画《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对象代码设计之美的更多相关文章

  1. 极客技术专题【007期】:jQuery初学者入门 - jQuery Event

    日期:2013-8-19  来源:GBin1.com 技术专题介绍 专题:jQuery初学者入门[第三讲:jQuery Event] 分享人:极客标签技术编辑 -Lana (请站内关注分享人) 授课时 ...

  2. 安全意识第八期丨OMG!发个帖子竟然摊上大事了

    互联网时代,话在网上说.钱在网上花.事在网上办,这早已成为一种习惯,越来越多的人也倾向于通过网络来获取信息. 借助现代信息技术,网络传播者通过即时通讯工具.微博.朋友圈等渠道发布信息,虽然传播起来更便 ...

  3. 《C专家编程》第四章——令人震惊的事实:数组和指针并不相同

    数组和指针是C语言里相当重要的两部分内容,也是新手程序员最容易搞混的两个地方,本章我们锁定指针与数组,探讨它们的异同点. 首先来看指针与数组在声明上的区别: int a[10]; int *p; 很明 ...

  4. web 9个令人震惊的WebGL示例

    20个使用WebGL和Three.js实现的网页场景 https://www.open-open.com/news/view/9d8136 20个使用WebGL和Three.js实现的网页场景 htt ...

  5. 2015年传智播客JavaEE 第168期就业班视频教程day45-ERP项目-01 10-类图结构分析设计

    运行astah-pro.bat,这是windows下运行的.astah-run.sh是Linux下运行的. 类结构视图的作用是描述类模型和模型与模型之间的关系,也就是说我们在这要把这个一对多和多对多的 ...

  6. 发短信的主要代码(SmsManger)

    SmsManager smsManager=SmsManager.getDefault(); smsManager.sendTextMessage(number,null,sms, null,null ...

  7. 剑指offer-高质量的代码

    小结: 规范性:书写清晰.布局清晰.命名合理 完整性:完成基本功能.考虑边界条件.做好错误处理 鲁棒性:采取防御性编程.处理无效输入 面试这需要关注 输入参数的检查 错误处理和异常的方式(3种) 命名 ...

  8. AngularJS 指循环数组对象

    <!DOCTYPE html><html><head><meta http-equiv="Content-Type" content=&q ...

  9. 浅谈测试驱动开发(TDD)

    测试驱动开发(TDD)是极限编程的重要特点,它以不断的测试推动代码的开发,既简化了代码,又保证了软件质量.本文从开发人员使用的角度,介绍了 TDD 优势.原理.过程.原则.测试技术.Tips 等方面. ...

随机推荐

  1. Error: Couldn't find preset "env" relative to directory "/Users/user/ethereumjs-vm"

    运行npm run build时遇见这个问题,解决办法是安装: npm install --save-dev babel-preset-env 就解决了

  2. HDU1753 (大正小数相加)

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=1753 大明A+B Time Limit: 3000/1000 MS (Java/Others)    ...

  3. Mysql利用binlog日志恢复数据操作(转)

    a.开启binlog日志:1)编辑打开mysql配置文件/etc/mys.cnf[root@vm-002 ~]# vim /etc/my.cnf在[mysqld] 区块添加 log-bin=mysql ...

  4. Putty等工具中解决SSH连接超时断开的问题

    转自:http://www.putty.ws/putty-chaoshi 1 在 linux下的ssh命令:vim /etc/ssh/ssh_config 然后找到里面的ServerAliveInte ...

  5. java ssm 后台框架平台 项目源码 websocket即时聊天发图片文字 好友群组 SSM源码

    官网 http://www.fhadmin.org/D 集成安全权限框架shiro  Shiro 是一个用 Java 语言实现的框架,通过一个简单易用的 API 提供身份验证和授权,更安全,更可靠E ...

  6. rem布局简介

    移动端常见布局: 1.流式布局 高度固定,宽度自适应 2.响应式布局 能够用一套代码适应不同尺寸屏幕 3.rem布局 宽高自适应,能实现整个页面像一张图片一样缩放且不失真的效果. rem布局: em: ...

  7. Redis全方位详解--数据类型使用场景和redis分布式锁的正确姿势

    一.Redis数据类型 1.string string是Redis的最基本数据类型,一个key对应一个value,每个value最大可存储512M.string一半用来存图片或者序列化的数据. 2.h ...

  8. CentOS7 安装.netcore 2 部署应用出现An assembly specified in the application dependencies manifest (xxx.deps.json)

    # dotnet xxx.dll Error: An assembly specified in the application dependencies manifest (xxx.deps.jso ...

  9. python基础学习1-类属性访问

    #!/usr/bin/env python # -*- coding:utf-8 -*- #====> __setattr__ 重写 设置类对象属性值时候调用的魔法方法 __getattr__( ...

  10. Codeforces 911D. Inversion Counting (数学、思维)

    题目链接:Inversion Counting 题意: 定义数列{ai|i=1,2,...,n}的逆序对如下:对于所有的1≤j<i≤n,若ai<aj,则<i,j>为一个逆序对. ...