Python进阶_数据模型
[TOC]
Python数据模型
数据模型 ?:对Python框架的描述,规范了语言自身构建模块的接口。
一致性:任何获取对象长度的方法都是 len()
。 Pythonic
如何达到一致性的呢?
对任何对象 obj
,在使用 len(obj)
时,Python解释器会调用特殊方法,即 obj._len_()
。只需要重写 __len__
方法,就能够使用通用的 len()
来获取对象长度。
Magic Method
特殊方法。
__getitem & __len
len(obj)
andobj.__len__()
obj[key]
andobj.__getitem__(key)
Example 1:Card
1 | import collections |
collections.namedtuple(
, [ 用于构建只有少数属性而没有方法的对象。])
1 | # 牌堆类 |
''' French playing cards (jeu de cartes) are cards that use the French suits of trèfles (clovers or clubs♣), carreaux (tiles or diamonds♦), cœurs (hearts♥), and piques (pikes or spades♠). Each suit contains three face cards; the valet (knave or jack), the dame (lady or queen), and the roi (king). '''
可以看出,magic method
有以下好处:
1、对于任意类,直接使用 magic method
就可以完成类的标准操作,不需要考虑获取长度是.length()
还是.size()
等形式。
2、可以使用像 random
这样的内置库达到定义的类的标准操作,不需要重写方法。
由于 class deck
中 __getitem__
方法将操作 []
的对象定义为 self._cards
,因此__getitem__
方法对应的操作 []
能够在 class deck
上实现纸牌的切片,实际上是 self._cards
的切片。
1 | # deck的切片 |
同样,__getitem__
方法也导致 class deck
可迭代(__iter__
)
1 | # deck的迭代和反向迭代 |
如何实现排序?
按照点数:min = 2, max = A,按照花色:spades > hearts > diamonds > clubs
1 | # 定义纸牌的位置权重 |
洗牌功能使用
__setitem__
实现。
通过实现__len__
、__getitem__
,class FrenchDeck
就几乎等同于Python数据类型:列表,能够实现列表的排序、切片等操作。
Attention!
实现如此简便的纸牌类的关键有两个:
1、唯一内置隐藏属性对应着 List
这一数据类型,使得具有形式:deck = FrenchDeck()
、deck[:3]
,简洁;
2、重载 magic method
使得类的操作具有普遍性,Pathonic。
magic method
在除元编程以外的环境下很少用到,这是Python解释器调用的;
元编程:使用代码生成代码
对于__len__
来说,CPython中直接读取 PyVarObject
(可变内存对象)的C语言结构体的 ob_size
属性,更快;
运算符重载
Example 2:Vector
1 | class Vector: |
Vector类重定义了__add__
、__mul__
、__bool__
、__abs__
、__repr__
内置方法,使得利用运算符+
的向量的加法、利用*
的向量的数乘、利用abs()
的向量取模符合定义,并且可以利用bool()
判断向量是否为非零向量。
Attention!
- 使用
__repr__
表达对象:
1 | # 一般直接输出对象是这样的: |
“Difference between str and repr in Python”(http://stackoverflow.com/questions/1436703/differencebetween-str-and-repr-in-python)是 Stack Overflow 上的一个问题,Python 程序员 Alex Martelli 和 Martijn Pieters 的回答很精彩。
- 使用
__bool__
进行真值判断:
1 | return bool(self.x or self.y) |
比起使用以下语句更加高效
1 | return bool(abs(self)) |
需要注意的是,我们定义的对象总被认为是 True,除非该对象重载了 __bool__
或 __len__
。
调用顺序:__bool__
-> __len__
Magic Method List
非运算符魔术方法:
类别 | 方法名 |
---|---|
字符串/字节序列表示形式 | __repr__ 、 __str__ 、 __format__ 、 __byte__ |
数值转换 | __abs__ 、 __bool__ 、 __complex__ 、 __int__ 、 __format__ 、 __hash__ 、 __index__ |
集合模拟 | __len__ 、 __getitem__ 、 __setitem__ 、 __delitem__ 、 __comtains__ |
迭代枚举 | __iter__ 、 __reversed__ 、 __next__ |
可调用模拟 | __call__ |
上下文管理 | __enter__ 、 __exit__ |
实例创建和销毁 | __new__ 、 __init__ 、 __del__ |
属性管理 | __getattr__ 、 __getattribute__ 、 __setattr__ 、 __delattr__ 、 __dir__ |
属性描述符 | __get__ 、 __set__ 、 __delete__ |
跟类相关的服务 | __prepare__ 、 __instancecheck__ 、 __subcalsscheck__ |
运算符相关特殊方法:
类别 | 方法名及对应运算符 |
---|---|
一元运算符 | __neg__ -、__pos__ +、__abs__ |
比较运算符 | __lt__ <、__le__ <=、__eq__ ==、__ne__ !=、__gt__ >、__ge__ >= |
算术运算符 | __add__ +、__sub__ -、__mul__ *、__truediv__ /、__floordiv__ //、 __mod__ %、__divmod__ divmod()、__pow__ **、__round__ round() |
反向算术运算符 | __radd__ 、__rsub__ 、__rmul__ 、__rtruediv__ 、__rfloordiv__ 、__rmod__ 、__rdivmod__ 、__rpow__ |
增量赋值算术运算符 | __iadd__ 、__isub__ 、__imul__ 、__itruediv__ 、__ifloordiv__ <br 、__imod__ 、__ipow__ |
位运算符 | __invert__ ~、__lshift__ <<、__rshift__ >>、__and__ &、__or__ |
反向位运算符 | __rlshift__ 、__rrshift__ 、__rand__ 、__rxor__ 、__ror__ |
增量赋值位运算符 | __ilshift__ 、__irshift__ 、__iand__ 、__ixor__ 、__ior__ |
Advantages
为什么要使用魔术方法?
从FrenchDeck类的示例可以知道,Magic Method
使得自定义的类(数据模型,以特有方式构建、储存数据)能够在各种操作方法上表现得像内置数据类型一般,这种共同性是Python简洁明了、表达力强的原因之一。
Python语言参考手册,“Data Model”:https://docs.python.org/3/reference/datamodel.html
Martelli's Stack Overflow:http://stackoverflow.com/users/95810/alexmartelli
平衡的艺术
为什么将 len()
作为一个特殊方法?如果 x
是内置类型,那么 len(x)
会直接从C结构体中提取属性,不调用方法,速度非常快。如果不是内置类型,则使用 __len__
。因此 len()
作为特殊方法在内置类型的效率和语言的一致性上达成平衡。