投稿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源码剖析》这本书。对了,我有电子版的,有需要的可以私信我。

原文地址:https://www.cnblogs.com/xyou/p/9509508.html

时间: 2024-08-29 12:59:50

投稿007期|令人震惊到发指的PyObject对象代码设计之美的相关文章

新订单、新反馈信息、新投稿 各种场景 各种给管理员发通知邮件

一.设置管理员邮件地址和发邮参数后台,系统设置,系统参数设置基本属性标签:管理员邮箱.以后的通知邮件都会发到这一邮箱FTP/EMAIL标签:如果主机支持mail函数,则个人感觉用这个效率可能高一点,主机不支持的话也没关系,还可以用SMTP,设置方法跟邮件客户端一样.需要说明的是,如果想用gmail的帐号发送实现起来比较麻烦,gmail的smtp需要ssl,且不说帝国自带的邮件发送类不支持ssl,就是帝国后台设置邮件的地方也没有提供ssl选项,所以就不折腾了,用国内的邮箱帐号发就是了. 二.新增给

实在忍不住了,发一段神级代码,大家瞻仰瞻仰

跟这样的函数比起来,顿觉日月无光,天昏地暗,飞沙走石! 完全一派末日景象啊! 另外命名什么的就先无视吧……跟这么强大的封装比起来,命名算个屁啊! ----说下背景吧-------------- 上述代码是我强调了多次,MVC必须要用Model,然后要注意封装,结果这姐姐(工作2年多了都)就封装成这个德行了 -------------------- 同样一个函数,既能返回一个对象,还能返回这个对象中的某个属性啊服了工作这么多年第一次见这样的神级代码 .到处new,new你妹啊new! ------

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

数组和指针是C语言里相当重要的两部分内容,也是新手程序员最容易搞混的两个地方,本章我们锁定指针与数组,探讨它们的异同点. 首先来看指针与数组在声明上的区别: int a[10]; int *p; 很明显的,第一个是数组a,第二个是指针p.下一个问题是a的类型是什么?p的类型是什么?a[0]的类型是int,而a是个数组名,它是否表示整个数组呢?事实并非如此,a是一个指针常量,是一个指向int的指针常量,而p是一个指向int的指针,是一个变量.这是它们的第一个区别:一个是常量,一个是变量.那么常量和

发一下今天晚上打的代码

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-cn"> <head> <met

&lt;剑指offer&gt;采用javascript代码来编程(1-66)

采用的是牛客网上的判题系统javascript(V8 6.0.0) 1.二维数组的查找 function Find(target, array) { // write code here var row = array.length; // 行数 var col = array[0].length; // 列数 // 从右上角开始比较 var r = 0; var c = col-1; while(r <=row-1 && c >= 0) { // 注意这里有等于号 if (t

剑指offer-高质量的代码(调整数组顺序使得奇数位于偶数的前面)

题目:输入一个整数数组,实现一个函数中调整该数组中数字的顺序,使得所有的奇数位于数组的前半部,所有偶数位于数组的后半部. 思路:用两个指针p1和p2,分别指向数组的头和尾部,p1只向后移,p2只向前移.当满足p1<p2这个条件时,可以将p1向后移并找到第一偶数为止,p2向前移直到找到第一个奇数为止,此时如果还满足p1<p2的条件,则交换p1和p2的值. c++代码:本代码扩展性的体现为:将函数bool (*func) (int)当做参数传人preorder中,而此时要判断数字的奇偶性,可以写一

浅谈测试驱动开发(TDD)

1. 优势 TDD的基本思路就是通过测试来推动整个开发的进行.而测试驱动开发技术并不只是单纯的测试工作. 需求向来就是软件开发过程中感觉最不好明确描述.易变的东西.这里说的需求不只是指用户的需求,还包括对代码的使用需求.很多开发人员最害怕的就是后期还要修改某个类或者函数的接口进行修改或者扩展,为什么会发生这样的事情就是因为这部分代码的使用需求没有很好的描述.测试驱动开发就是通过编写测试用例,先考虑代码的使用需求(包括功能.过程.接口等),而且这个描述是无二义的,可执行验证的. 通过编写这部分代码

(转)浅谈测试驱动开发(TDD)

测试驱动开发(TDD)是极限编程的重要特点,它以不断的测试推动代码的开发,既简化了代码,又保证了软件质量.本文从开发人员使用的角度,介绍了 TDD 优势.原理.过程.原则.测试技术.Tips 等方面. 2 评论: 李群 ([email protected])www.ihere.org 背景 一个高效的软件开发过程对软件开发人员来说是至关重要的,决定着开发是痛苦的挣扎,还是不断进步的喜悦.国人对软件蓝领的不屑,对繁琐冗长的传统开发过程的不耐,使大多数开发人员无所适从.最近兴起的一些软件开发过程相关

测试驱动开发TDD(一)TDD的好处及介绍

背景 一个高效的软件开发过程对软件开发人员来说是至关重要的,决定着开发是痛苦的挣扎,还是不断进步的喜悦.国人对软件蓝领的不屑,对繁琐冗长的传统开发过程的不耐,使大多数开发人员无所适从.最近兴起的一些软件开发过程相关的技术,提供一些比较高效.实用的软件过程开发方法.其中比较基础.关键的一个技术就是测试驱动开发(Test-Driven Development).虽然TDD光大于极限编程,但测试驱动开发完全可以单独应用.下面就从开发人员使用的角度进行介绍,使开发人员用最少的代价尽快理解.掌握.应用这种