larken

勤奋的人生才有价值

导航

第1章 Python数据模型

#《流畅的Python》读书笔记
# 第一部分 序幕
# 第1章 Python数据模型
# 魔术方法(magic method)是特殊方法的昵称。于是乎,特殊方法也叫双下方法(dunder method)。

# 1.1 一摞Python风格的纸牌

# 示例 1-1 一摞有序的纸牌 import collections Card=collections.namedtuple('Card',['rank','suit']) 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): return len(self._cards) def __getitem__(self, position): return self._cards[position] # 如下面这个控制台会话所示,利用namedtuple,我们可以很轻松地得到一个纸牌对象: >>> beer_card=Card('7','diamond') >>> beer_card Card(rank='7', suit='diamond') # 首先,它跟任何标准Python集合类型一样,可以用len()函数来查看一叠牌有多少张: >>> deck=FrenchDeck() >>> len(deck) 52 #从一叠牌中抽取特定的一张纸牌,比如说第一张或最后一张,是很容易的:deck[0]或deck[-1]。这都是由__getitem__方法提供的: >>> deck[0] Card(rank='2', suit='spades') >>> deck[-1] Card(rank='A', suit='hearts') #Python 已经内置了从一个序列中随机选出一个元素的函数random.choice >>> from random import choice >>> choice(deck) Card(rank='J', suit='hearts') >>> choice(deck) Card(rank='2', suit='clubs') >>> choice(deck) Card(rank='10', suit='diamonds') #下面列出了查看一摞牌最上面3张和只看牌面是A的牌的操作。 >>> deck[:3] [Card(rank='2', suit='spades'), Card(rank='3', suit='spades'), Card(rank='4', suit='spades')] >>> deck[12::13] [Card(rank='A', suit='spades'), Card(rank='A', suit='diamonds'), Card(rank='A', suit='clubs'), Card(rank='A', suit='hearts')] #另外,仅仅实现了__getitem__方法,这一摞牌就变成可迭代的了: >>> for card in deck: print(card) Card(rank='2', suit='spades') Card(rank='3', suit='spades') Card(rank='4', suit='spades') Card(rank='5', suit='spades') ... #反向迭代也没关系: >>> for card in reversed(deck): print(card) Card(rank='A', suit='hearts') Card(rank='K', suit='hearts') Card(rank='Q', suit='hearts') Card(rank='J', suit='hearts') ... #in运算符就会按顺序做一次迭代搜索 >>> Card('Q','hearts') in deck True >>> Card('7','beats') in deck False #梅花2的大小是0,黑桃A是51: >>> suit_values=dict(spades=3,hearts=2,diamonds=1,clubs=0) >>> def spades_high(card): rank_value=FrenchDeck.ranks.index(card.rank) return rank_value*len(suit_values)+suit_values[card.suit] #有了spades_high函数,就能对这摞牌进行升序排序了: >>> for card in sorted(deck,key=spades_high): print(card) Card(rank='2', suit='clubs') Card(rank='2', suit='diamonds') Card(rank='2', suit='hearts') Card(rank='2', suit='spades') Card(rank='3', suit='clubs') ... # 1.2 如何使用特殊方法 # 首先明确一点,特殊方法的存在是为了被Python解释器调用的,你自己并不需要调用它们。也就是说没有my_object.__len__()这种写法,而应该使用len(my_object)。在执行len(my_object)的时候,如果my_object是一个自定义类的对象,那么Python会自己去调用其中由你实现的__len__方法。
# 很多时候,特殊方法的调用是隐式的,比如for i in x:这个语句,背后其实用的是iter(x),而这个函数的背后则是x.__iter__()方法。当然前提是这个方法在x中被实现了。 # 通常你的代码无需直接使用特殊方法。除非有大量的元编程存在,直接调用特殊方法的频率应该远远低于你去实现它们的次数。唯一的例外可能是__init__方法,你的代码里可能经常会用到它,目的是在你自己的子类的__init__方法中调用超类的构造器。 # 通过内置的函数(例如len、iter、str,等等)来使用特殊方法是最好的选择。这些内置函数不仅会调用特殊方法,通常还提供额外的好处,而且对于内置的类来说,它们的速度更快。14.12节中有详细的例子。 # 不要自己想当然地随意添加特殊方法,比如__foo__之类的,因为虽然现在这个名字没有被Python内部使用,以后就不一定了。 #示例 1-2 一个简单的二维向量类 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) # 如果想让Vector.__bool__更高效,可以采用这种实现: # def __bool__(self): # return bool(self.x or self.y) def __bool__(self): return bool(abs(self)) def __add__(self, other): x=self.x+other.x y=self.y+other.y def __mul__(self,scalar): return Vector(self.x*scalar,self.y*scalar) #为了给这个类型设计API,我们先写个模拟的控制台会话来做doctest. #下面这一段代码就是向量加法: >>> v1 = Vector(2, 4) >>> v2 = Vector(2, 1) >>> v1 + v2 Vector(4, 5) #为了保持一致性,我们的API在碰到abs函数的时候,也应该返回该向量的模: >>> v = Vector(3, 4) >>> abs(v) 5.0 #我们还可以利用*运算符来实现向量的标量乘法: >>> v * 3 Vector(9, 12) >>> abs(v * 3) 15.0 # Python有一个内置的函数叫repr,它能把一个对象用字符串的形式表达出来以便辨认,这就是“字符串表示形式”。 # repr就是通过 __repr__这个特殊方法来得到一个对象的字符串表示形式的。 # 如果没有实现__repr__,当我们在控制台里打印一个向量的实例时,得到的字符串可能会是<Vector object at 0x10e100070>。 # __repr__和__str__的区别在于,后者是在str()函数被使用,或是在用print函数打印一个对象的时候才被调用的,并且它返回的字符串对终端用户更友好。 # 1.3 特殊方法一览 # 表1-1:跟运算符无关的特殊方法 # 表1-2:跟运算符相关的特殊方法 # 1.4 为什么len不是普通方法 # 换句话说,len之所以不是一个普通方法,是为了让Python自带的数据结构可以走后门,abs也是同理。 # 但是多亏了它是特殊方法,我们也可以把len用于自定义数据类型。 # 这种处理方式在保持内置类型的效率和保证语言的一致性之间找到了一个平衡点,也印证了“Python之禅”中的另外一句话:“不能让特例特殊到开始破坏既定规则。” # 1.5 本章小结 # 通过实现特殊方法,自定义数据类型可以表现得跟内置类型一样,从而让我们写出更具表达力的代码——或者说,更具Python风格的代码。 # Python对象的一个基本要求就是它得有合理的字符串表示形式,我们可以通过__repr__和__str__来满足这个要求。前者方便我们调试和记录日志,后者则是给终端用户看的。这就是数据模型中存在特殊方法__repr__和__str__的原因。 # 对序列数据类型的模拟是特殊方法用得最多的地方,这一点在FrenchDeck类的示例中有所展现。在第2章中,我们会着重介绍序列数据类型,然后在第10章中,我们会把Vector类扩展成一个多维的数据类型,通过这个练习你将有机会实现自定义的序列。 # Python通过运算符重载这一模式提供了丰富的数值类型,除了内置的那些之外,还有decimal.Decimal和fractions.Fraction。这些数据类型都支持中缀算术运算符。在第13章中,我们还会通过对Vector类的扩展来学习如何实现这些运算符,当然还会提到如何让运算符满足交换律和增强赋值。 #Python数据模型的特殊方法还有很多,本书会涵盖其中的绝大部分,探讨如何使用和实现它们。 # 1.6 延伸阅读

 

posted on 2019-07-17 19:41  larken  阅读(333)  评论(0编辑  收藏  举报