《流畅的Python第⼆版》读书笔记——Python数据模型
引⾔
这是《流畅的Python第⼆版》抢先版的读书笔记。Python版本暂时⽤的是python-3.8。为了使开发更简单、快捷,本⽂使⽤了JupyterLab。
Python解释器调⽤⼀些特殊⽅法来进⾏基本的对象操作,这种特殊⽅法通常有特殊的写法。⽐如__getitem__,如果你实现了该⽅法,就可以通过obj[key]来触发obj.__getitem__(key)⽅法。
这些特殊⽅法名能让你⾃⼰的对象实现和⽀持以下的语⾔构架,并与之交互:
集合类
属性访问
迭代
运算符重载
函数和⽅法的调⽤
对象的创建和销毁
使⽤await的异步编程
字符串表⽰形式和格式化
管理上下⽂(即 with 块)
有些⼈也称这些特殊⽅法为魔术⽅法(magic method)。
A Pythonic Card Deck
本节这个例⼦虽然简单,但是它通过实现__getitem__和__len__⽅法来展⽰特殊⽅法的强⼤。
frenchdeck.py:
import collections
# 利⽤namedtuple构造⼀个简单的表⽰纸牌的类
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]
我们可以像下⾯这样很⽅便的构造Card对象:
from frenchdeck import Card,FrenchDeck
beer_card = Card('7','diamonds')
beer_card
但本例的重点是FrenchDeck类(⼀叠纸牌),它虽然很⼩,但很强⼤。像标准的Python集合⼀样,可以调⽤len()函数来获取对象中Card实例的数量:
deck = FrenchDeck()
len(deck)
还可以从⼀叠纸牌中抽取第⼀张和最后⼀张,很简单deck[0]、deck[-1]即可,这是__getitem__⽅法提供的:
Python提供了⼀个函数从序列中随机获取元素:random.choice,我们可以直接⽤在deck实例上:
from random import choice
choice(deck)
现在我们已经看到了实现特殊⽅法来利⽤Python数据模型的两个好处:
作为你的类的⽤户,⽆需记忆标准操作的各种名称(⽐如如何获取元素个数,通过.size()还是.length())。
更加⽅便地利⽤Python标准库,避免重复造轮⼦。就像random.choice函数⼀样。
因为__getitem__⽅法把[]操作委托(delegates)给了self._cards列表,我们的deck⾃动⽀持切⽚。下⾯展⽰了如何查看牌堆中前三张牌,然后通过从第13张牌开始(索引是12),每隔13张牌抽⼀张牌,来选取牌A:
deck[:3]
deck[12::13]
⽽且,仅仅是实现了__getitem__特殊⽅法,我们的deck还可以迭代:
for card in deck:
print(card)
(上图只截取部分)
同时也可以反向迭代:
for card in reversed(deck):
print(card)
迭代通常是隐式的,如果⼀个集合没有实现__contains__⽅法,那么in运算符就会执⾏⼀个顺序扫描。于是,in 运算符可以⽤在我们的FrenchDeck 类上,因为它是可迭代的:
Card('Q','hearts')in deck
Card('7','beasts')in deck
还可以实现排序,⽐如⽤点数来判断纸牌⼤⼩,2最⼩、A最⼤;同时⿊桃(spades)最⼤、红桃(hearts)次之、⽅块(diamonds)再次、梅花(clubs)最⼩。下⾯就是按照这个规则来排序的函数,约定梅花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)
# 点数乘以4 + 花⾊值
java调用python模型return rank_value *len(suit_values)+ suit_values[card.suit]
有了这个函数,就你可以对牌堆进⾏升序排序了:
for card in sorted(deck, key=spades_high):
print(card)
通过实现__len__和__getitem__这两个特殊⽅法,FrenchDeck就跟⼀个Python⾃有的序列数据类型⼀
样,可以体现出Python的核⼼语⾔特性(例如迭代和切⽚)。同时这个类还可以⽤于标准库中random.choice、reversed和sorted这些函数。另外,对于组合的运⽤是
的__len__和__getitem__的具体实现可以委托给self._cards这个list对象。
如何使⽤特殊⽅法
⾸先要知道,特殊⽅法是被Python解释器⽽不是你调⽤的,你不能写my_object.__len__(),⽽是写len(my_object):Python会调⽤你实现
的__len__⽅法。
然后如果是Python内置类型,⽐如列表、字符串、字节序列(bytearray)等,那么解释器会⾛捷径,__len__实际上会直接返
回PyVarObject⾥的ob_size属性,直接读取这个值⽐调⽤⼀个⽅法要快很多。
很多时候,特殊⽅法调⽤是隐式的。⽐如语句for i in x:实际上会导致调⽤iter(x),⽽背后解释器会调⽤x.__iter__(),如果有的这个⽅法的话。否则像FrenchDeck例⼦⼀样,调⽤x.__getitem__()。
通常你⽆需直接调⽤特殊⽅法,除⾮有⼤量的元编程存在。唯⼀的例外可能是__init__⽅法。
通过内置函数来使⽤特殊⽅法是最好的选择。
不要想当然的随意添加特殊⽅法,⽐如__foo__之类的,因为虽然这个名字现在没有被Python内部使⽤,以后就不⼀定了。模拟数值类型
利⽤特殊⽅法,可以让⾃定义对象通过加号+等运算符进⾏运算。
我们实现⼀个⼆维向量类vector,就是我们数学中的向量。
上图是⼀个向量加法的例⼦:Vector(2,4)+Vector(2,1) = Vector(4,5)
我们实现的向量类应该能做这样的加法,通过+运算符:
>>> v1 = Vector(2,4)
>>> v2 = Vector(2,1)
>>> v1 + v2
Vector(4,5)
其中+运算符得到的结果也是⼀个向量,并且打印出来的结果很友好。
我们的向量也应该⽀持abs函数,返回的是向量的模·:
>>> v = Vector(3,4)
>>>abs(v)# sqrt(3**2+4**2)
5.0
还可以利⽤*运算符实现向量的标量乘法(向量与数的乘法):
>>> v *3
Vector(9,12)
>>>abs(v *3)
15.0
下⾯就来实现这样⼀个Vector类,上⾯提到的操作在代码中是⽤__repr__、__abs__、__add__和__mul__实现的:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。