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() 作为特殊方法在内置类型的效率和语言的一致性上达成平衡。