1.1 通过一段python风格的扑克牌代码来理解
1、理解双下滑线的函数__init__和__len__等
2、理解 继承。python2中需要显示写FrenchDeck(object),python3中继承关系是默认的。可以直接用FrenchDeck.属性。
import collections
# 定义一张纸牌,'数值','花色'。利用nametuple可以得到一个纸牌对象
Card = collections.namedtuple('Card',['rank','suit'])
class FrenchDeck:
ranks = [str(i) for i in range(2,11)]+list('JQKA')
suits = 'spades diamonds clubs hearts'.split(' ')
def __init__(self):
self._cards = [Card(r, s) for s in self.suits for r in self.ranks]
def __len__(self):
return len(self._cards)
def __getitem__(self, position):
return self._cards[position]
def __setitem__(self, key, value): # 可以定义洗牌函数来实现洗牌功能。
pass
Card_all = FrenchDeck() # 实现一个完整的扑克牌
print(len(Card_all)) # 这是因为__len__
print(Card_all[0],Card_all[-1]) # 这是由__getitem__得到的
beer_Card = Card('7','spades') # 定义某一个牌的数字以及花色 得到一个纸牌对象
print(beer_Card)
from random import choice
print(choice(Card_all)) # 对于随机抽取一张牌,不需要单独写一个方法了,因为已经有内置函数choice()可以用
# 仅仅通过__getitem__即可实现牌的迭代
# for i in Card_all: # 以及反向迭代reversed(Card_all)
# print(i)
# 对这摞牌排序。虽然Card_all集成object类,但是对于排序需要额外的函数实现排序功能。
def spades_high(card):
suit_value = dict(spades=3, diamonds=2, clubs=1, hearts=0)
rank_value = FrenchDeck.ranks.index(card.rank)
return rank_value*len(suit_value)+suit_value[card.suit]
for i in sorted(Card_all,key=spades_high):
print(i)
1.2 如何使用特殊方法
特殊方法的存在是为了被 Python 解释器调用的,没有 my_object.len() 这种写法,而应该使用 len(my_object)。在执行 len(my_object) 的时候,如果 my_object 是一个自定义类的对象,那么 Python 会自己去调用其中由你实现的 len 方法。
通常你的代码无需直接使用特殊方法。除非有大量的元编程存在。唯一的例外可能是 init 方法,常用到,为了自己的子类的 init 方法中调用超类的构造器。
1.2.1 模拟数值类型
一个例子 : Vector(2,4) + Vextor(2,1) = Vector(4,5)
ps:Python 内置的 complex 类可以用来表示二维向量,但我们这个自定义的类Vector可以扩展到 n 维向量。
abs 是一个内置函数,如果输入是整数或者浮点数,它返回的是输入值的绝对值;如果输入是复数(complex number),那么返回这个复数的模。
上述例子包含了一个 Vector 类的实现,上面提到的操作在代码里是用这些特殊方法实现
的:repr、abs、add 和 mul。
import math
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 __str__(self):
# return 'Vector(%r,%r)' % (self.x,self.y)
# def __str__(self):
# return 'Vector({x},{y})'.format(x = self.x,y = self.y)
def __abs__(self):
return math.hypot(self.x,self.y) # hypot() 返回欧几里德范数 sqrt(x*x + y*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, other):
x = self.x*other
y = self.y*other
return Vector(x,y)
v1 = Vector(3,4)
v2 = Vector(2,1)
print(v1+v2) # Vector(5,5)
print(v1*2) # Vector(6,8)
print(abs(v1)) # 5.0
虽然代码里有 6 个特殊方法,但这些方法(除了 init)并不会在这个类自身的代码中使用。即便其他程序要使用这个类的这些方法,也不会直接调用它们,就像我们在上面的控制台对话中看到的。上文也提到过,一般只有 Python 的解释器会频繁地直接调用这些方法。接下来看看每个特殊方法的实现。
1.2.2 字符串表示形式(函数__repr__)
上节__repr__这个方法是用来得到一个对象的字符串的形式。如果没有实现 repr,当我们在控制台里打印一个向量的实例时,得到的字符串可能会是 <Vector object at 0x10e100070>。
也暗示了一个关键:Vector(1, 2) 和 Vector(‘1’, ‘2’) 是不一样。
同样,str.format 函数所用到的新式字符串格式化语法也是利用了 repr,才把 !r 字段变成字符串。
repr 和 str 的区别在于,后者是在 str() 函数被使用,或是在用 print 函数打印一个对象的时候才被调用的,并且它返回的字符串对终端用户更友好。
如果你只想实现这两个特殊方法中的一个,repr 是更好的选择,因为如果一个对象没有 str 函数,而 Python 又需要调用它的时候,解释器会用 repr 作为替代。
1.2.3 算术运算符(函数__add__和__mul__)
通过 add 和 mul,示例 1-2 为向量类带来了 + 和 * 这两个算术运算符。中缀运算符的基本原则就是不改变操作对象,而是产出一个新的值。
1.2.4 自定义的布尔值(函数__bool__)
我们对__bool__ 的实现很简单,如果一个向量的模是 0,那么就返回 False,其他情况则返回 True。因为__bool__ 函数的返回类型应该是布尔型,所以我们通过bool(abs(self)) 把模值变成了布尔值。
1.3 特殊方法
当交换两个操作数的位置时,就会调用反向运算符(b * a 而不是 a * b)。增量赋值运算符则是一种把中缀运算符变成赋值运算的捷径(a = a * b 就变成了 a*= b)。
1.4 为什么len不是普通方法
如果 x 是一个内置类型的实例,那么 len(x) 的速度会非常快。CPython 会直接从一个 C 结构体里读取对象的长度,完全不会调用任何方法。获取一个集合中元素的数量是一个很常见的操作,在 str、list、memoryview等类型上,这个操作必须高效。
len 之所以不是一个普通方法,是为了让 Python 自带的数据结构可以走后门,abs 也是同理。
1.5 本章小结
通过实现特殊方法,自定义数据类型可以表现得跟内置类型一样,从而让我们写出更具表达力的代码——或者说,更具 Python 风格的代码。
Python 对象的一个基本要求就是它得有合理的字符串表示形式,我们可以通过__repr__和__str__来满足这个要求。前者方便我们调试和记录日志,后者则是给终端用户看的。这就是数据模型中存在特殊方法__repr__ 和__str__ 的原因。
对序列数据类型的模拟是特殊方法用得最多的地方,这一点在 FrenchDeck 类的示例中有所展现。
Python 通过运算符重载这一模式提供了丰富的数值类型,除了内置的那些之外,还有decimal.Decimal 和 fractions.Fraction。这些数据类型都支持中缀算术运算符。在第 13 章中,我们还会通过对 Vector 类的扩展来学习如何实现这些运算符,当然还会提到如何让运算符满足交换律和增强赋值。