阅读笔记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编辑  收藏  举报

导航