1. 程式人生 > >投稿007期|令人震驚到發指的PyObject對象代碼設計之美

投稿007期|令人震驚到發指的PyObject對象代碼設計之美

member enable 找到 投稿 釋放 素數 程序開發 應用 剖析

前言

最近在重溫經典漫畫《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對象代碼設計之美