• Python源码剖析阅读笔记 :第一章:Python对象初探


    Python对象初探

    对象是python中的核心概念,Python中一切皆对象。一种内置数据类型也是一种对象,我们称之为内置类型对象(int,string,list等)。这些类型对象实现了面向对象中‘类’的概念。使用时,就可以实例化出这些内置类型对象。

    第一章只需要了解python对象是如何被c实现的。python对象整个体系在第二章会有完整讲解。
    

    1.1python内的对象

    在python中,对象就是为C中的结构体在堆上申请的一块内存,一般来说,对象是不能被静态初始化的,并且也不能在栈空间上生存。唯一的例外就是类型对象,Python中所有的内建的类型对象都是被静态初始化的。

    python对象创建后,内存中的大小是不变的。如果那些需要容纳可变长度数据的对象(list,dict等可变类型的实例化对象)
    ,只能维护一个指向一块可变大小的内存区域的指针。这样会使维护对象的工作变得简单。从对数据的操作变成了对指针的操作,工作由繁化简了。

    1.1.1对象机制的基石——PyObject

    在python中,所有的东西都是对象,而所有的对象都拥有一些相同的内容,这些内容在pyobject中定义。
    [object.h]

    typedef struct_object{
          PyObject_HEAD
    };
    

    这个struct就是对象机制中的基石,从代码中可以看到,Python对象的秘密都隐藏在PyObject_HEAD这个宏中。

    #ifdef Py_TRACE_REFS
    /* Define pointers to support a doubly-linked list of all live heap objects. */
    #define _PyObject_HEAD_EXTRA            
        struct _object *_ob_next;           
        struct _object *_ob_prev;
    
    #define _PyObject_EXTRA_INIT 0, 0,
    
    #else
    #define _PyObject_HEAD_EXTRA
    #define _PyObject_EXTRA_INIT
    #endif
    

    实际发布python中,PyObject的定义非常简单:

    typedef struct _object {
        _PyObject_HEAD_EXTRA
        Py_ssize_t ob_refcnt;
        struct _typeobject *ob_type;
    } PyObject;
    

    ob_refcnt是与python的内存管理机制有关,它实现了基于引用计数的垃圾收集机制。另一个是指向结构体的指针*ob_type,用来说明创建的对象是什么类型的一个类型对象。

    定长对象和变长对象

    整数对象的特殊信息是一个C中的整型变量,无论这个整数对象的值有多大,都可以保存在这个整形变量(ob_ival)中。但是并不是所有对象都可以这么定义。比如字符串。所以需要添加‘变量的个数’这一变量(ob_size)来控制个数。

    typedef struct {
        PyObject ob_base;
        Py_ssize_t ob_size; /* Number of items in variable part */
    } PyVarObject;
    

    我们把整数对象这样不包含可变长度数据的对象成为‘定长对象’,而字符串对象这样包含可变长度数据的对象成为‘变长对象’。在python内部,每一个对象都拥有相同的对象头部。这就使得在python中,对对象的引用变得非常统一,我们只需要一个PyObject*指针就可以引用任意一个对象。

    类型对象

    上面大概的表述,python所有对象共有信息的定义。python对象的主要部分。当内存中分配空间,创建对象的时候,必须要知道申请多大的空间。然而它是不确定的。

    typedef struct _typeobject {
        PyObject_VAR_HEAD
        const char *tp_name; /* For printing, in format "<module>.<name>" 调试时使用输出格式<module>.<name> */
        Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation python对象创建时内存大小的信息 */
         ....
    

    1.2.1对象的创建

    有两种方式来创建python对象,第一种:python C API来创建,第二种:类型对象PYInet_Type。
    Python的C API分成两类,一类成为范型的API,或者称为AOL(abstract object layer)。这类API都形如:PyObject_***。可以应用任何python对象身上。对于创建一个整数对象,我们可以采用如下表达式:PyObject* intObj = PyObject_New(PyObject, &PyInt_Type)

    另一类是与类型相关的API,COL(Concrete Object layer)。这类通常只能作用在某一种类型的对象上,对于每一种内建对象,都会有对应的API。
    例子:PyObject *intObj = PyInt_FromLong(10)创建了一个整数为10的变量。

    1.2.2对象的行为

    在PyTypeObject中定义了大量的指针,这些函数指针最终都会指向某个函数,或是指向NULL。这些函数指针可以视为类型对象中所定义的操作,而这些操作直接决定着一个对象在运行时所表现的行为。

    #ifdef Py_LIMITED_API
    typedef struct _typeobject PyTypeObject; /* opaque */
    #else
    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;
        PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2)
                                        or tp_reserved (Python 3) */
        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 */
        unsigned 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;
    
        /* 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;
    
        destructor tp_finalize;
    
    #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;
    
    

    在这些操作信息中,有三组非常重要的操作族,他们是

        /* Method suites for standard classes */
    
        PyNumberMethods *tp_as_number;    /*数值对象 */
        PySequenceMethods *tp_as_sequence; /*序列对象 */
        PyMappingMethods *tp_as_mapping;      /* 关联对象*/
    

    对于一种类型来说,它完全可以同时定义三个函数中的所有操作。换句话说,一个对象可以即表现出数值对象的特性,也可以表现出关联对象的特性。举个例子:

    class MyInt(int):
       def __getitem__(self, key):
            return key + str(self)
    
    a = MyInt(1)
    
    b = MyInt(2)
    
    print(a + b)
    print(a['key'])
    

    结果:

    看上去a['key']这样的操作时一个类似于dict这样的对象才会支持的操作。从int继承出来的Myint应该自然就是一个数值对象,重写上面的方法之后,可以视为指定MyInt在Python内部对应的PyTypeObject对象的tp_as_mapping.mp_subscript操作。最终这个Myint的实例对象可以“表现”得像一个关联对象。本质上就是在于PyTypeObject中允许一种类型同事指定三种不同行为的对象。

    1.2.3 类型的类型

    PyTypeObject中,定义的最开始,可以发现PyObject_VAR_HEAD,这意味着Python中的类型实际上也是一种对象。每一个对象都是对应一种类型的。而类型对象是什么类型呢?对于其他对象,可以通过与其关联的类型对象确定其类型,那么通过什么来确定一个对象是类型对象呢?

    答案就是PyType_Type:

    PyTypeObject PyType_Type = {
        PyVarObject_HEAD_INIT(&PyType_Type, 0)
        "type",                                     /* tp_name */
        sizeof(PyHeapTypeObject),                   /* tp_basicsize */
        sizeof(PyMemberDef),                        /* tp_itemsize */
        (destructor)type_dealloc,                   /* tp_dealloc */
        0,                                          /* tp_print */
        0,                                          /* tp_getattr */
        0,                                          /* tp_setattr */
        0,                                          /* tp_reserved */
        (reprfunc)type_repr,                        /* tp_repr */
        0,                                          /* tp_as_number */
        0,                                          /* tp_as_sequence */
        0,                                          /* tp_as_mapping */
        0,                                          /* tp_hash */
        (ternaryfunc)type_call,                     /* tp_call */
        0,                                          /* tp_str */
        (getattrofunc)type_getattro,                /* tp_getattro */
        (setattrofunc)type_setattro,                /* tp_setattro */
        0,                                          /* tp_as_buffer */
        Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
            Py_TPFLAGS_BASETYPE | Py_TPFLAGS_TYPE_SUBCLASS,         /* tp_flags */
        type_doc,                                   /* tp_doc */
        (traverseproc)type_traverse,                /* tp_traverse */
        (inquiry)type_clear,                        /* tp_clear */
        0,                                          /* tp_richcompare */
        offsetof(PyTypeObject, tp_weaklist),        /* tp_weaklistoffset */
        0,                                          /* tp_iter */
        0,                                          /* tp_iternext */
        type_methods,                               /* tp_methods */
        type_members,                               /* tp_members */
        type_getsets,                               /* tp_getset */
        0,                                          /* tp_base */
        0,                                          /* tp_dict */
        0,                                          /* tp_descr_get */
        0,                                          /* tp_descr_set */
        offsetof(PyTypeObject, tp_dict),            /* tp_dictoffset */
        type_init,                                  /* tp_init */
        0,                                          /* tp_alloc */
        type_new,                                   /* tp_new */
        PyObject_GC_Del,                            /* tp_free */
        (inquiry)type_is_gc,                        /* tp_is_gc */
    };
    

    PyType_Type在python的类型机制中是一个至关重要的对象,所有用户自定义的class所对应的PyTypeObject对象都是通过这个对象创建的。图中显示了一般的PyType_Object和PyType_Type的关系:

    <type.type>是python内部的PyType_Type,它是所有class的class,所以它在Python中被称为metaclass。

    Py-nt_Type是怎么和PyType_Type联系上的呢。前面提到,python中,每一个对象都将自己的引用计数、类型信息保存在开始的部分中。为了方便对内存的初始化,python提供了几个宏。

    1.3 Python对象的多态性

    Python 创建一个对象比如 PyLongObject 时,会分配内存进行初始化,然后 Python 内部会用 PyObject* 变量来维护这个对象,其他对象也与此类似
    所以在 Python 内部各个函数之间传递的都是一种范型指针 PyObject* 我们不知道这个指针所指的对象是什么类型,只能通过所指对象的 ob_type 域 动态进行判断,而 Python 正是通过 ob_type 实现了多态机制
    比如:

    void Print(PyObject* object)
    {
          object->ob_type->tp_print(object);
    }
    

    传进来的指针可以是int型也可以是string类型,他们都会调用他们相应的类型对象中定义的输出操作。这就是多态性的展现。

    1.4引用计数

    Python 通过引用计数来管理维护对象在内存中的存在与否
    Python 中的每个东西都是一个对象, 都有ob_refcnt 变量,这个变量维护对象的引用计数,从而最终决定该对象的创建与销毁
    在 Python 中,主要通过 Py_INCREF(op)Py_DECREF(op) 这两个宏 来增加和减少对一个对象的引用计数。当一个对象的引用计数减少到 0 之后, Py_DECREF将调用该对象的tp_dealloc来释放对象所占用的内存和系统资源;
    但这并不意味着最终一定会调用 free 释放内存空间。因为频繁的申请、释放内存会大大降低 Python 的执行效率。因此 Python 中大量采用了内存对象池的技术,使得对象释放的空间归还给内存池而不是直接free,后续使用可先从对象池中获取

    // Include/object.h
    #define _Py_NewReference(op) (                          
        _Py_INC_TPALLOCS(op) _Py_COUNT_ALLOCS_COMMA         
        _Py_INC_REFTOTAL  _Py_REF_DEBUG_COMMA               
        Py_REFCNT(op) = 1)
    
    #define Py_INCREF(op) (                         
        _Py_INC_REFTOTAL  _Py_REF_DEBUG_COMMA       
        ((PyObject *)(op))->ob_refcnt++)
    
    #define Py_DECREF(op)                                   
        do {                                                
            PyObject *_py_decref_tmp = (PyObject *)(op);    
            if (_Py_DEC_REFTOTAL  _Py_REF_DEBUG_COMMA       
            --(_py_decref_tmp)->ob_refcnt != 0)             
                _Py_CHECK_REFCNT(_py_decref_tmp)            
            else                                            
                _Py_Dealloc(_py_decref_tmp);                
        } while (0)
    

    1.5Python对象的分类

    大致分为5类。

    • Fundanmental对象:类型对象
    • Numeric对象:数值对象
    • Sequence对象:容纳其他对象的序列集合对象
    • Mapping对象:具有映射关系的对象
    • Internal对象: Python虚拟机在运行时内部使用的对象

      这些是对python对象的一些基本了解。
  • 相关阅读:
    Design and Analysis of Algorithms_Decrease-and-Conquer
    TCPL 札记
    谬论:64 = 65?
    二叉树内部顶点与外部顶点在数量上的关系
    Design and Analysis of Algorithms_Divide-and-Conquer
    LeetCode 36. Valid Sudoku
    LeetCode 58. Length of Last Word
    LeetCode 66. Plus One
    LeetCode 67. Add Binary
    LeetCode 70. Climbing Stairs
  • 原文地址:https://www.cnblogs.com/sometingintheway/p/12820518.html
Copyright © 2020-2023  润新知