Python 数据模型

Python018

Python 数据模型,第1张

Python 风格的关键完全体现在 Python 的数据模型上,数据模型所描述的 API ,为使用最地道的语言特性来构建开发者自己的对象提供了工具。

当 Python 解析器遇到特殊句法时,会使用特殊方法去激活一些基本的对象操作。特殊方法以双下划线开头,以双下划线结尾。如: obj[key] 的背后就是 __getitem__ 方法。魔术方法是特殊方法的昵称,特殊方法也叫双下方法。

使用 __getitem__ 和 __len__ 创建一摞有序的纸牌:

上面的例子,使用 collections.namedtuple 构建了一个简单的类来表示一张纸牌, namedtuple 用以构建只有少数属性但没有方法的类。

我们自定义的 FrenchDeck 类可以像任何 python 标准集合类型一样使用 len() 函数,查看一叠牌有多少张:

也可以像列表一样,使用位置索引, d[i] 将调用 __getitem__ 方法:

也可以使用标准库模块提供的 random.choice 方法,从序列中随机选取一个元素。下面,我们如随机取出一张纸牌:

现在我们已经体会到通过 python 特殊方法,来使用 Python 数据模型的 2 个好处:

因为 __getitem__ 方法把 [] 操作交给了 self.cards 列表,所以我们的 FrenchDeck 实例自动支持切片:

仅仅实现了 __getitem__ 方法,这一摞牌即变得可迭代:

运行结果:

也可以直接调用内置的 reversed 函数,反向迭代 FrenchDeck 实例:

运行结果:

迭代通常是隐式的,比如一个集合类型没有实现 __contains__ 方法,那么 in 运算符就会按顺序做一次迭代搜索。

因此, in 运算符可以用在我们的 FrenchDeck 实例上,因为它是可迭代的:

FrenchDeck 还可以使用 Python 标准库中的 sorted 函数,实现排序:

首先定义一个排序依据的函数:

优先按 rank 的大小排序,rank 相同时则比较 suit 的值:

运行结果:

优先按 suit 的大小排序,suit 相同时则比较 rank 的值:

运行结果:

按照目前的设计,FrenchDeck 还不支持洗牌,因为它是不可变的:

shuffle 函数要调换集合中元素的位置,而 FrenchDeck 只实现了不可变的序列协议,可变的序列还必须提供 __setitem__ 方法:

洗牌:

没有任何的返回值,可见 random.shuffle 就地修改了可变序列 d 。为便于观察结果,我们定义输入的输出函数:

运行结果:

每次洗牌,都是一个随机的序列:

首先明确一点,特殊方法的存在是为了被 Python 解析器调用的,例如:我们不会使用 obj.__len__() 这种写法,而是 len(obj) 。在执行 len(obj) 时,如果 obj 是一个自定义类的对象,那么 Python 会自己去调用我们实现的 __len__ 方法。

对于 Python 内置的数据类型,比如列表、字符串、字节序列等,那么 CPython 会抄个近路, __len__ 实际上会返回 PyVarObject 里的 ob_size 属性,这是因为直接读取属性比调用一个方法要快得多。

很多时候,特殊方法的调用是隐式的,比如 for i in x: 这个语句其实是调用 iter(x) ,而这个函数的背后是 x.__iter__() 方法。

通过内置函数如来使用特殊方法是最好的选择。这些内置函数不仅会调用这些方法,通常还提供额外的好处,对于内置类型来说,它们的速度更快。

下面,我们通过定义一个简单的二维向量类,再来体会一下 Python 特殊方法的美妙:

使用 Vector 类,就像使用 Python 内置的数据类型一样简单:

学python的人都知道,python中一切皆是对象,如class生成的对象是对象,class本身也是对象,int是对象,str是对象,dict是对象...。所以,我很好奇,python是怎样实现这些对象的?带着这份好奇,我决定去看看python的源码,毕竟源码才是满足自己好奇心最直接的方法。

在object.h文件中,定义了两种数据结构PyObject和PyVarObject,代码如下:

1 #define PyObject_HEAD                \ 2     Py_ssize_t ob_refcnt              \ 3     struct _typeobject *ob_type4  5 #define PyObject_VAR_HEAD       \ 6     PyObject_HEAD                       \ 7     Py_ssize_t ob_size8  9 typedef struct _object {10     PyObject_HEAD11 } PyObject12 13 typedef struct {14     PyObject_VAR_HEAD15 } PyVarObject

这两种数据结构分别对应python的两种对象:固定长度对象和可变长度对象。python中的所有对象都属于这两种对象中的一种,如int,float是固定长度对象,list,str,dict是可变长度对象。从上面两种对象数据结构定义来看,可变长度对象和固定长度对象的头都是PyObject结构体,也就是说python中所有对象的开头都包含这个结构体,并且可以用PyObject *指针来访问任何对象,这种访问对象的方法在python的源码中随处可见。PyObject结构体包含两个成员,ob_refcnt和ob_type指针。ob_refcnt用来表示对象被引用的次数,当ob_refcnt == 0时,这个对象会被立即销毁;ob_type指针指向了一个_typeobject类型的结构体,表示对象所属的类型,也就是生成该对象的类型,这其实很类似于面向对象中类与实例的关系,PyObject是某个类的实例,ob_type表示这个类。但与面向对象不同的是,ob_type本身也是个对象,我们来看下_typeobject的定义:

1 typedef struct _typeobject { 2     PyObject_VAR_HEAD 3     const char *tp_name/*类型名 */ 4     Py_ssize_t tp_basicsize, tp_itemsize/* 实例化对象的大小 */ 5  6     /* 标准方法 */ 7  8     destructor tp_dealloc9     printfunc tp_print10     getattrfunc tp_getattr11     setattrfunc tp_setattr12     cmpfunc tp_compare13     reprfunc tp_repr14 15     /* 标准类(数值类,列表类,dict类)方法*/16 17     PyNumberMethods *tp_as_number18     PySequenceMethods *tp_as_sequence19     PyMappingMethods *tp_as_mapping20 21     /* 其它标准方法*/22 23     hashfunc tp_hash24     ternaryfunc tp_call25     reprfunc tp_str26     getattrofunc tp_getattro27     setattrofunc tp_setattro28     ...  

29 } PyTypeObject

从上面定义来看,_typeobject的开头也包含了PyObject结构体,所以它也是一个对象,既然它也是一个对象,那么按照面向对象的理解,它又是谁来生成的呢?答案是所有PyTypeObject对象都是通过PyType_Type来生成的,包括PyType_Type本身,因为PyType_Type也是PyTypeObject对象,有点绕。PyType_Type的定义是通过将PyType_Type声明为全局静态变量实现的,具体如下:

1 PyTypeObject PyType_Type = { 2     PyVarObject_HEAD_INIT(&PyType_Type, 0) 3     "type",                                     /* tp_name */ 4     sizeof(PyHeapTypeObject),                   /* tp_basicsize */ 5     sizeof(PyMemberDef),                        /* tp_itemsize */ 6     (destructor)type_dealloc,                   /* tp_dealloc */ 7     0,                                          /* tp_print */ 8     0,                                          /* tp_getattr */ 9     0,                                          /* tp_setattr */10     0,                                  /* tp_compare */11     (reprfunc)type_repr,                        /* tp_repr */12     0,                                          /* tp_as_number */13     0,                                          /* tp_as_sequence */14     0,                                          /* tp_as_mapping */15     (hashfunc)_Py_HashPointer,                  /* tp_hash */16     (ternaryfunc)type_call,                     /* tp_call */17     0,                                          /* tp_str */18     (getattrofunc)type_getattro,                /* tp_getattro */19     (setattrofunc)type_setattro,                /* tp_setattro */20     0,                                          /* tp_as_buffer */21     ...22 }

从PyType_Type定义来看,ob_type被初始化为它自己的地址,所以PyType_Type的类型就是自己。从python源码实现来看,所有PyTypeObject的ob_type都会指向PyType_Type对象,所以PyType_Type是所有类型的类型,称之为元类。python中定义了很多内建的类型对象,如PyInt_Type (int类型),PyStr_Type (str类型),PyDict_Type(dict类型) 类型对象,下面看下PyInt_Type类型的定义:

1 PyTypeObject PyInt_Type = { 2     PyVarObject_HEAD_INIT(&PyType_Type, 0) 3     "int", 4     sizeof(PyIntObject), 5     0, 6     (destructor)int_dealloc,                    /* tp_dealloc */ 7     (printfunc)int_print,                       /* tp_print */ 8     0,                                          /* tp_getattr */ 9     0,                                          /* tp_setattr */10     (cmpfunc)int_compare,                       /* tp_compare */11     (reprfunc)int_to_decimal_string,            /* tp_repr */12     &int_as_number,                             /* tp_as_number */13     0,                                          /* tp_as_sequence */14     0,                                          /* tp_as_mapping */15     (hashfunc)int_hash,                         /* tp_hash */16     0,                                          /* tp_call */17     ...18 }

从PyInt_Type定义来看,它主要包含了int数据类型相关的方法。PyInt_Type 类型对象的初始化和PyType_Type 类型类似,PyInt_Type类型的定义也是通过全局静态变量的方式实现的,除了PyInt_Type了下,所有python内建类型都是以这种方式定义的。这些类型产生的对象都会共享这些类型对象,包括这些类型定义的方法。

在python中,怎样查看对象的类型呢?有两种方法,一种是直接type:

1 >>>x = 12 >>>type(x)3 <type 'int'>

另一种是通过对象的__class__属性:

1 >>>x = 12 >>>type(x)3 <type 'int'>4 >>>x.__class__5 <type 'int'>

现在来看看int,str,dict这些类型的类型:1 <type 'int'>2 >>>type(int)3 <type 'type'>4 >>>type(str)5 <type 'type'>6 >>>type(dict)7 <type 'type'>8 >>>type(type)9 <type 'type'>从这个输出来看,int,str,dict这些类型的类型都是type,这也印证了前面说的,所有类型都是通过元类type生成的。