学习python一段时间会发现,一直都是用python做业务逻辑。每次都是为了解决问题,而解决问题。而python中丰富的库会让我们欣喜,但是也可能让我们变懒,真正对python的理解却没有增加多少。而fluent python是进阶python非常好的一本书。
Python的数据模型
Let's be pythonic!
# 例子来自fluent python第4页
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, item):
return self._cards[item]复制代码
有了这些方法以后,我们就可以像正常操作列表一下操作我们的 FrenchDeck 。
deck = FrenchDeck()
len(deck) #输出为52复制代码
由于提供了 __getitem__
方法,可以直接通过下标直接访问对应的位置的元素,甚至可以像列表那样进行切片操作和遍历操作。
deck[0] # Card(rank='2', suit='spades')
deck[1] # Card(rank='A', suit='hearts')
deck[0:3] # [Card(rank='2', suit='spades'), Card(rank='3', suit='spades'), Card(rank='4', suit='spades')]
for card in deck:
print card # 输出省略复制代码
需要指出的是遍历操作并不总是显式的。如果一个集合没有实现 __contains__
方法,则 in
操作就会进行顺序遍历操作。
至于排序操作,需要我们提供排序的依据,现在假设排序的是按照先看号码,再看花色的顺序,其中花色按照梅花,方块,红桃,黑桃的顺序。所以可以按照这个顺序,计算出每张牌的位置索引,如下:
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]复制代码
然后就可以进行排序操作了:
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')
... (46 cards ommitted)
Card(rank='A', suit='diamonds')
Card(rank='A', suit='hearts')
Card(rank='A', suit='spades')复制代码
通过实现 __len__
和 __getitem__
方法,我们就可以像操作 python 内置的数据类型那样操作我们的数据模型。不过两者依然有区别。在 CPython 中,当我们对内置的数据类型进行 len(x)
操作的时候,我们其实并没有调用任何方法,只是取得 C 结构中的一个域。这使得我们可以高效的操作很多内置类型,如 str, list, memoryview 等等。
模拟数学类型
我们都在高中学过矢量运算。
矢量操作基本的矢量操作包括,矢量相加,矢量求模,矢量和标量相乘等等。然而我们希望用我们习惯的内置操作 +
abs
*
来进行这些运算,所以需要我们自定义的数据模型实现一些特殊方法。
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)
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, scalar):
return Vector(self.x * scalar, self.y * scalar)复制代码
实现了这些特殊方法后,就可以直接使用运算符对我们的数据模型进行操作了
v1 = Vector(2,4)
v2 = Vector(2,1)
v1 + v2 # Vector(4,5)
v = Vector(3,4)
abs(v) # 5.0
v * 3 # Vector(9, 12)
abs(v * 3) # 15.0复制代码
值得指出的是 __repr__
方法,如果不实现这个方法的话。直接打印一个 Vector
对象可能会输出
0x10e100070>.复制代码
而定义了这个方法后,输出就变得相当易读
Vector(3,4)复制代码
当然,还可以定义 __str__
来自定义str(x)
的行为。如果只想实现一个函数的话,建议实现 __repr__
函数,因为当没有 __str__
,会调用__repr__
作为备用。