阅读笔记1:Python数据模型
Python数据模型概述
数据模型是对python框架的描述,它规范了这门语言自身构建模块的接口,这些模块包括但不限于序列,迭代器,函数,类和上下文管理器
不管在哪个框架下写程序,都会花费大量时间去实现那些会被框架本身调用的方法,python也不例外。python解释器碰到特殊的句法时,会使用特殊方法去激活一些基本的对象操作,这些特殊方法的名字以两个下划线开头,以两个下划线结尾(例如: __getitem__):
为了求my_collection[key]的值,解释器实际上会调用my_collection.__getitem__(key)
这些特殊方法名能让你自己的对象实现和支持以下的语言框架,并与之交互:
迭代
集合类
属性访问
运算符重载
函数和方法的调用
对象的创建和销毁
字符串表示形式和格式化
管理上下文(with块)
**魔术方法(magic method)是特殊方法的昵称。特殊方法也可以叫双下方法(dunder method)
Example1: 实现__gettime__和__len__两个特殊方法:
import collections Card = collections.namedtuple('Card', ['rank', 'suit']) class FrenchDeck(object): ranks = [str(n) for n in range(2, 11)] + list('JQKA') suits = 'spades diamonds clubs hearts'.split() def __init__(self): self._cards = [Card(rank, suit) for suit in self.suits for rank in self.ranks] def __len__(self): return len(self._cards) def __getitem__(self, position): return self._cards[position]
虽然FrenchDeck继承了object类,但是功能却不是继承而来的,我们通过数据模型和一些合成来实现这些功能。
通过实现__len__和__getitem__这两个特殊方法,它就跟一个python自有的序列数据类型一样,可以体现出python的核心语言特性(比如迭代和切片)。同时这个类还可以用于标准库中诸如random.choice, reversed 和sorted函数。
如何使用特殊方法
特殊方法的存在是为了被python解释器调用的。在执行len(my_object)的时候,如果my_object是一个自定义的对象,那么python会去调用其中由你实现的__len__方法
备注:
如果是python内置的类型,比如列表,字符串,字节序列等等,那么CPython会炒个近路,__len__实际上会返回PyVarObject里的ob_size属性。PyVarObject是表示内存中长度可变的内置对象的C语言结构体,直接读取这个值比调用一个方法要快很多
很多时候,特殊方法的调用是隐式的,比如for i in x: 这个语句,背后其实用的是iter(x),而且这个函数的背后是x.__iter__()方法,当然前提是这个方法在x中实现了
通常自己的代码无需直接使用特殊方法,除非有大量的元编程存在。唯一的例外可能是__init__方法,目的是在自己的子类的__init__方法中调用超类的构造器
通过内置的函数(len/iter/str 等等)来使用特殊方法是最好的选择,这些内置函数不仅会调用特殊方法,还提供额外的好处,对于内置类来说,它们的速度更快
Example2: 实现__repr__, __abs__, __add__和__mul__,__bool__
from math import hypot class Vector: def __init__(self, x=0, y=0): self.x = x self.y = y def __repr__(self): return 'Vector(%r, %r)' % (self.x, self.y) def __abs__(self): return hypot(self.x, self.y) def __bool__(self): return bool(abs(self)) def __add__(self, other): x = self.x + other.x y = self.y + other.y return Vector(x, y) def __mul__(self, scalar): return Vector(self.x * scalar, self.y * scalar)
1)字符串表示形式
python有一个内置的函数就叫repr,它能把一个对象用字符串的形式表达出来以便辨认。repr就是通过__repr__这个特殊方法来得到一个对象的字符串表示形式的
交互式控制台和调试程序用repr函数来获取字符串表示形式;在老的使用%符号的字符串格式中,这个函数返回的结果用来代替%r所代表的对象;同样,str.format函数所用到的新式字符串格式化语法,也是利用了repr,才把!r字段变成字符串
__repr__和__str__的区别在于:后者是在str()函数被使用,或是在用print函数打印一个对象的时候才被调用的,并且它返回的字符串对终端用户更友好
如果只想实现这两个特殊方法中的一个,__repr__是更好的选择,如果一个对象没有__str__函数,而python又需要调用它的时候,解释器会用__repr__作为替代
2)算术运算符
通过__add__和__mul__,带来了+和*这两个算术运算符;也就是说,执行+或者*的时候,会调用这两个特殊方法
这两个方法返回值都是新创建的向量对象,被操作的两个向量还是原封不动,代码里只是读取了他们的值而已,中缀运算符的基本原则就是不改变操作对象,而是产出一个新的值
3)自定义的布尔值
尽管python有bool类型,但实际上任何对象都可以用于需要布尔值的上下文中,比如if while;
为了判定一个值x是真还是假,python会调用bool(x), 这个函数只能返回True或者False;
默认情况下,我们自定义的类的实例总被认为是真的,除非这个类对__bool__或者__len__有自己的实现;
bool(x)背后调用的是x.__bool__()的结果,如果不存在__bool__方法,那么bool(x)会尝试调用x.__len__(),如果返回0,那么bool会返回False,否则返回True
小结:
通过实现特殊方法,自定义数据类型可以表现得跟内置类型一样,从而让我们写出更具python风格的代码
python对象的一个基本要求就是它得有合理的字符串表示形式,可以通过__repr__和__str__满足这个要求,前者方便我们调试和记录日志,后者给终端用户展示,这就是数据模型中存在特殊方法__repr__和__str__的原因
对序列数据类型的模拟是特殊方法用得最多的地方,参考example1
python通过运算符重载这一模式提供了丰富的数值类型,参考example2
参考官方文档:
posted on 2018-09-13 15:47 cherryjing0629 阅读(285) 评论(0) 编辑 收藏 举报