流畅的python——1 数据模型

一、数据模型

当python解释器遇到特殊的句法时,会调用特殊方法,比如:d['a'] 会调用 __getitem__

magic 和 dunder:魔法方法 magic method是特殊方法的昵称。特殊方法也叫 双下方法 dunder method

具名元组 namedtuple:用于构建只有少数属性,但是,没有方法的对象。

import collections

Card = collections.namedtuple('Card',['rank','suit'])

特殊方法:__len____getitem__

class FrenchDeck:
     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):  # 调用 len() ,触发 __len__ 特殊方法
     	return len(self._cards)
     def __getitem__(self, position):  # 调用 f[0] ,触发 __getitem__ 方法
     	return self._cards[position]
In [6]: f = F()

In [8]: len(f)
Out[8]: 52

In [9]: f[0]
Out[9]: Card(rank='2', suit='spades')

random.choice 从序列中随机取一个元素,注意:此时的 f 是一个对象

In [11]: random.choice(f)
Out[11]: Card(rank='10', suit='spades')

In [12]: f
Out[12]: <__main__.F at 0x2a3d7ee3588>
    
In [13]: for i in f:
    ...:     print(i)
    ...:
Card(rank='2', suit='spades')
Card(rank='3', suit='spades')
Card(rank='4', suit='spades')
Card(rank='5', suit='spades')
Card(rank='6', suit='spades')
Card(rank='7', suit='spades')
Card(rank='8', suit='spades')
Card(rank='9', suit='spades')
Card(rank='10', suit='spades')
...

因为 __getitem__方法把 [] 操作交给了 self._card 列表,所以,f 自动支持切片操作,即可迭代,迭代 f 对象,得到的结果是迭代 self._card 列表。

__getitem__ 实现该特殊方法:[] 和 可迭代

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

sorted 排序

In [20]: def sort_f(card):
    ...:     rank_value = F.ranks.index(card.rank)
    ...:     suit_dict = dict(spades=3,hearts=2,diamonds=1,clubs=0)
    ...:     return rank_value * len(suit_dict) + suit_dict[card.suit]
    ...:

In [21]: sorted(f,key=sort_f)

虽然 FrenchDeck 隐式地继承了 object 类,5 但功能却不是继承而来的。我们通过数据模型和一些合成来实现这些功能。通过实现__len____getitem__这两个特殊方法,FrenchDeck 就跟一个 Python 自有的序列数据类型一样,可以体现出 Python 的核心语言特性(例如迭代和切片)。同时这个类还可以用于标准库中诸如 random.choice、reversed 和 sorted 这些函数。另外,对合成的运用使得__len____getitem__ 的具体实现可以代理给 self._cards 这个 Python 列表(即 list 对象)。

特殊方法的存在是为了被python解释器调用的,不是我们自己来调用。比如,没有obj.__len()这种写法,应该使用len(obj) 。是执行 len 方法,然后,调用了 __len__ 方法。

对于python内置类型,__len__ 会返回 PyVarObject 的 ob_size 属性,PyVarObject 是表示内存中长度可变的内置对象的C语言结构体。直接读取这个值比调用一个方法要快的多。

for i in x: 对应的函数是 iter(x) ,调用的是 x.__iter__() 方法。

不要自己想当然地随意添加特殊方法,比如 foo 之类的,因为虽然现在这个名字没有被 Python 内部使用,以后就不一定了。

实现一个简单的向量类:

In [38]: class Vector:
    ...:     def __init__(self,x,y):
    ...:         self.x = x
    ...:         self.y = y
    ...:     def __abs__(self):  # abs方法
    ...:         return hypot(self.x,self.y)
    ...:     def __add__(self,other):  # + 运算符,中缀运算符
    ...:         return Vector(self.x + other.x,self.y + other.y)
    ...:     def __repr__(self):  # repr方法,终端打印
    ...:         return '({},{})'.format(self.x,self.y)
    ...:     def __bool__(self):  # bool方法
    ...:         return bool(abs(self))
    ...:     def __mul__(self,scalar):  # * 运算符,中缀运算符
    ...:         return Vector(self.x * scalar,self.y * scalar)

repr方法

Python 有一个内置的函数叫 repr,它能把一个对象用字符串的形式表达出来以便辨认,这就是“字符串表示形式”。

交互式控制台和调试程序(debugger)用 repr 函数来获取字符串表示形式;

在老的使用% 符号的字符串格式中,这个函数返回的结果用来代替 %r 所代表的对象;同样,str.format 函数所用到的新式字符串格式化语法

__repr____str__ 的区别:__str__str() 函数使用时,被调用;或是在 print 函数调用

两个方法,__repr__更好一点,因为如果一个对象没有 __str__ 函数,而需要调用它时,解释器会用__repr__作为替代而调用。

__bool__ 方法

尽管 Python 里有 bool 类型,但实际上任何对象都可以用于需要布尔值的上下文中(比如if 或 while 语句,或者 and、or 和 not 运算符)。为了判定一个值 x 为真还是为假,Python 会调用 bool(x),这个函数只能返回 True 或者 False。

默认情况下,我们自己定义的类的实例总被认为是真的,除非这个类对 __bool__ 或者__len__ 函数有自己的实现。bool(x) 的背后是调用 x.__bool__() 的结果;如果不存在 __bool__ 方法,那么 bool(x) 会尝试调用 x.__len__()。若返回 0,则 bool 会返回False;否则返回 True。

In [49]: a = Vector(0,0)

In [50]: a
Out[50]: (0,0)

In [51]: if a:
    ...:     print('kkk')
    ...:

In [52]:

如果想让 Vector.__bool__ 更高效,可以采用这种实现:

def __bool__(self):
    return bool(self.x or self.y)

它不那么易读,却能省掉从 abs 到__abs__到平方再到平方根这些中间步骤。通过bool 把返回类型显式转换为布尔值是为了符合__bool__对返回值的规定,因为 or运算符可能会返回 x 或者 y 本身的值:若 x 的值等价于真,则 or 返回 x 的值;否则返回 y 的值。

为什么 len 不是普通方法:因为 对于内置类型 len方法 是直接读取属性,获取长度。

通过实现特殊方法,自定义数据类型可以表现得跟内置类型一样,从而让我们写出更具表达力的代码——或者说,更具 Python 风格的代码。

数据模型和对象模型;魔术方法与特殊方法;元对象协议=对象模型:构建核心语言的API

posted @ 2021-07-22 14:33  pythoner_wl  阅读(93)  评论(0编辑  收藏  举报