《流畅的python》第一章 Python数据模型

这一章主要讲述如何用特殊方法去激活一些基本的对象操作,这些特殊方法的名字以两个下划线开头,以两个下划线结尾(例如getitem)比如obj[key]的背后就是getitem方法,为了能求得my_collection[key]的值,解释器实际上会调用my_collection.getitem(key)。
本章的实例是一摞python风格的纸牌,很有嚼头。
1.1纸牌实例

第一个示例1-1 建立了一个纸牌类:

import collections #【1】

Card = collections.namedtuple('Card', ['rank', 'suit'])

class FrenchDeck:#【2】
    ranks = [str(n) for n in range(2, 11)] + list('JQKA')
    suits = 'spades diamonds clubs hearts'.split()

    def __init__(self):#【3】
        self._cards = [Card(rank, suit) for suit in self.suits
                                        for rank in self.ranks]

    def __len__(self):#【4】
        return len(self._cards)

    def __getitem__(self, position):#【5】
        return self._cards[position]

【1】collections模块,Python拥有一些内置的数据类型,比如str, int, list, tuple, dict等, collections模块在这些内置数据类型的基础上,提供了几个额外的数据类型:
1.namedtuple(): 生成可以使用名字来访问元素内容的tuple子类
2.deque: 双端队列,可以快速的从另外一侧追加和推出对象
3.Counter: 计数器,主要用来计数
4.OrderedDict: 有序字典
5.defaultdict: 带有默认值的字典
个人理解,这个namedtuple就有点像一个临时表,例子中的Card是表名,rank和suit是字段名。
【2】一个短小精悍的类,这个对ranks和suits数据集进行了赋值,同时重构了init/len/getitem这三个方法。
【3】用self._cards创建一副完整的扑克牌实例
【4】计算这幅扑克牌的长度
【5】获取每张扑克牌的位置


想一想,如果自己写一副类似扑克牌,要多少代码,而高手的代码就是这么简洁高效。
用上面的代码,可以轻松的得到一个纸牌对象,比如:

deck=FrenchDeck()
print(len(deck))

从一叠牌里面抽取特定的一张也很简单:deck[0]或者deck[-1],这里面0是正向数第一张扑克牌,-1是逆向数第一张。
如果想随机抽取一张纸牌也很简单,用random.choice,代码如下:

from random import choice
choice(deck)

可以看到,每次抽取的纸牌都不一样,这就是造好的随机数轮子(我们不需要自己去生成随机数然后再挑选相应的对象,一个函数搞定),很方便。
因为getitem方法把[]操作交给了self._cards列表,所以我们的deck类自动支持切片,例如查看一摞牌最上面3张和只看牌面是A的牌的操作:

deck[:3]
deck[12::13]#【6】

【6】这里12的意思是索引号为12的牌A,后面的13表示每隔13张抽一次。要把正副扑克牌显示出来也很easy,可迭代的deck用一个for循环就可以便利:

for card in deck:
print(card)

反向迭代同样很简单:

for card in reversed(deck):
print(card)

接下来做一个稍微复杂的操作,排序,就是用点数来判定扑克牌的大小,2最小,A最大;同时黑桃最大,红桃次之,方块再次,梅花最小。

suit_values=dict(spades=3,hearts=2,diamonds=1,clubs=0)
def spades_high(card):
    rank_value=FrenchDeck.ranks.index(card.rank)#【7】
    return  rank_value * len(suit_values)+suit_values[card.suit]

下面调用spades_high函数对这摞牌进行升序排序

for card in sorted(deck,key=spades_high):
    print(card)

【7】这段代码是不是有点难懂,慢慢研究一下。 rank_value的值等于FrenchDeck类下面ranks列表的索引值index(card.rank),card是一个Card类的对象,具有rank和suit两种属性。

1.2如何使用特殊方法
特殊方法的存在是为了被Python解释器调用的,程序员无需直接调用这些方法,也就是说没有my_object.len()这种写法,而应该使用len(my_object)。
本部分举了一个二维向量的例子,下面是定义的一个Vector类,这个类重构了repr/abs/bool/add/mul这些特殊方法

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)
print(v1+v2)

调用abs函数也没有问题:

v=Vector(3,4)
print(abs(v))

需要注意的是%r和%s的区别
%r用rper()方法处理对象
%s用str()方法处理对象
有些情况下,两者处理的结果是一样的,比如说处理int型对象。
本书的作者认为,%和str.format这两种格式化字符串的手段在本书中都会使用,但作者偏向于str.format,python程序员更喜欢%,这两种形式并存的情况还会持续下去。

你可能感兴趣的:(读书笔记)