流畅的Python (Fluent Python) —— 第一部分

Python 最好的品质之一是一致性。
魔术方法(magic method)是特殊方法的昵称。特殊方法也叫双下方法。

1.1 一摞Python风格的纸牌 

 1 import collections
 2 Card = collections.namedtuple('Card', ['rank', 'suit'])  # 创建了一个有名字的元组
 3 
 4 
 5 class FrenchDeck:  # 隐式继承了Object类
 6     ranks = [str(n) for n in range(2, 11)] + list('JQKA')  # 可选的序号
 7     suits = 'spades diamonds clubs hearts'.split()  # 可选的花色
 8 
 9     def __init__(self):  # 创建该类的对象时,会执行此方法
10         self._cards = [Card(rank, suit) for suit in self.suits
11                        for rank in self.ranks]
12 
13     def __len__(self):  # 调用 len(deck) 时,实际上是执行 len.__len__ 方法
14         return len(self._cards)
15 
16     def __getitem__(self, position):  # 调用 deck[0] 时,实际上是执行 deck.__getitem__(key=0)
17         return self._cards[position]
18 
19 
20 deck = FrenchDeck()
21 print(len(deck))  # 判断个数的定义,是由__len__实现的
22 print(deck[0])  # 根据位置抽取,此方法是由__getitem__实现的

 

通过实现特殊方法来利用 Python 数据模型的两个好处 :

1. 作为你的类的用户,他们不必去记住标准操作的各式名称(怎么得到元素的总数? 是 .size() 还是 .length() 还是别的什么? )。
2. 可以更加方便地利用 Python 的标准库,比如 random.choice 函数,从而不用重新发明轮子。

 

同时,__getitem__ 方法把  [ ]  操作交给了 self._cards 列表,所以我们的 deck 类自动支持切片(slicing)操作。 另外,仅仅实现了 __getitem__ 方法,这一摞牌就变成可迭代的了 。

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

对牌堆进行排序:

 1 # 对牌堆排序
 2 suit_values = dict(spades=3, hearts=2, diamonds=1, clubs=0)
 3 
 4 
 5 def spades_high(card):
 6     rank_value = FrenchDeck.ranks.index(card.rank)
 7     '''
 8     card.rank:某张牌对象的rank属性(2-A),FrenchDeck.ranks.index(card.rank)返回该属性的位置
 9     len(suit_values): 花色的种类,也就是4
10     suit_values[card.suit]: 就是根据某张牌对象的花色,取出该花色对应的值(权重)
11     '''
12     return rank_value * len(suit_values) + suit_values[card.suit]  # 返回这张排在牌堆中的序号(唯一)
13 
14 
15 for card in sorted(deck, key=spades_high):
16     print(card)

如何洗牌

  按照目前的设计, FrenchDeck 是不能洗牌的,因为这摞牌是不可变的(immutable):卡牌和它们的位置都是固定的,除非我们破坏这个类的封装性,直接对 _cards 进行操作。第 11 章会讲到,其实只需要一行代码来实现 __setitem__方法,洗牌功能就不是问题了。

1.2 如何使用特殊方法

  首先明确一点,特殊方法(双下方法)的存在是为了被 Python 解释器调用的,你自己并不需要调用它们。

  然而如果是 Python 内置的类型,比如列表(list)、字符串(str)、字节序列(bytearray)等,那么 CPython 会抄个近路, __len__ 实际上会直接返回 PyVarObject 里的 ob_size 属性。 PyVarObject 是表示内存中长度可变的内置对象的 C语言结构体。直接读取这个值比调用一个方法要快很多。
  很多时候,特殊方法的调用是隐式的,比如 for i in x: 这个语句,背后其实用的是iter(x),而这个函数的背后则是 x.__iter__() 方法。当然前提是这个方法在 x 中被实现了。
  通常你的代码无需直接使用特殊方法。除非有大量的元编程(meta programming)存在,直接调用特殊方法的频率应该远远低于你去实现它们的次数。唯一的例外可能是 __init__ 方法,你的代码里可能经常会用到它,目的是在你自己的子类的 __init__ 方法中调用超类的构造器。通过内置的函数(例如 leniterstr,等等)来使用特殊方法是最好的选择。这些内置函数不仅会调用特殊方法,通常还提供额外的好处,而且对于内置的类来说,它们的速度更快。 14.12 节中有详细的例子。不要自己想当然地随意添加特殊方法,因为虽然现在这个名字没有被 Python 内部使用,以后就不一定了。

 1 from math import hypot
 2 
 3 
 4 class Vector:
 5     def __init__(self, x=0, y=0):
 6         self.x = x
 7         self.y = y
 8 
 9     '''
10     一个对象用字符串的形式表达出来以便辨认,如果没有实现 __repr__,当我们在控制台里打印一个向量的实例时,得到的字符串可能会是 <Vector object at 0x10e100070>。
11     '''
12     def __repr__(self):
13         return 'Vector(%r, %r)' % (self.x, self.y)
14     # __repr__方便我们调试和记录日志,
15     # __str__是给终端用户用的
16 
17     def __abs__(self):
18         return hypot(self.x, self.y)
19 
20     # 模是 0 就返回 False,其他返回 True
21     def __bool__(self):
22         # return bool(abs(self))
23         return bool(self.x or self.y)  # 高效写法
24 
25     # + 操作
26     def __add__(self, other):
27         x = self.x + other.x
28         y = self.y + other.y
29         return Vector(x, y)
30 
31     # * 操作
32     def __mul__(self, other):
33         return Vector(self.x * other, self.y * other)

  如果一个对象没有 __str__ 函数,而 Python 又需要调用它的时候,解释器会用 __repr__ 作为替代。




  

 

posted @ 2019-03-03 14:13  施浩宇  阅读(513)  评论(0编辑  收藏  举报