前言

最近在重温经典漫画《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. windows禁用端口命令

    netstat -aon|findstr 1099 找出占用1099端口的进程 然后关闭占用该端口的进程:taskkill -f -pid 3756(进程id)

  2. 访问 IIS 元数据库失败解决问题的方法

    近日调试一Asp.net程序,出现了“访问 IIS 元数据库失败”的错误信息,最后经过搜索发现了解决问题的方法. 解决方法如下: 1.依次点击“开始”-“运行”. 2.在“运行”栏内输入 “C:\WI ...

  3. 897. Increasing Order Search Tree

    题目来源: https://leetcode.com/problems/increasing-order-search-tree/ 自我感觉难度/真实难度:medium/easy 题意: 分析: 自己 ...

  4. PAT乙级1023

    1023 组个最小数 (20 分)   给定数字 0-9 各若干个.你可以以任意顺序排列这些数字,但必须全部使用.目标是使得最后得到的数尽可能小(注意 0 不能做首位).例如:给定两个 0,两个 1, ...

  5. java学习笔记-基础篇

    Java基础篇 1—12 常识 13 this关键字 14参数传递 16 继承 17 访问权限 28—31异常 1—12 常识 1.文件夹以列表展示,显示扩展名,在地址栏显示全路径 2.javac编译 ...

  6. C++ Primer 学习笔记_45_STL实践与分析(19)--泛型算法的结构

    STL实践与分析 --泛型算法的结构 引言: 正如全部的容器都建立在一致的设计模式上一样,算法也具有共同的设计基础. 算法最主要的性质是须要使用的迭代器种类.全部算法都指定了它的每一个迭代器形參可使用 ...

  7. RHEL 7.6 安装 Oracle 18c RAC

    RHEL 7.6 安装 Oracle 18c RAC 第一部分 安装规划 虚拟环境 VirtualBox 6.0 OS 版本 Red Hat Enterprise Linux Server relea ...

  8. ThreadLocal理解

    ThreadLocal 概述 ThreadLocal实例仅作为线程局部变量的==操作类==,以及==线程存储局部变量时的Key==.真正的线程局部变量是存储在各自线程的本地,通过Thread类中的Th ...

  9. ie中input光标问题

    为input 添加 readonly=”readonly”  UNSELECTABLE="on"   属性. IE中设置了readonly=”readonly”,点击使用日期选择器 ...

  10. canvas 绘制双线技巧

    楔子 最近一个项目,需要绘制双线的效果,双线效果表示的是轨道(类似铁轨之类的),如下图所示: 负责这块功能开发的小伙,姑且称之为L吧,最开始是通过数学计算的方式来实现这种双线,也就是在原来的路径的基础 ...