流畅的Python学习笔记之第一章 Python数据模型

版权声明:本文为博主原创文章,转载请声明出处并添加原文链接。 https://blog.csdn.net/azsx02/article/details/78523776

一张纸牌类

import collections

#用以构建只有少数属性但是没有方法的对象,比如数据库条目。
# nametuple返回一个类名为Card的tuple的子类
#这里表示一张纸牌类
Card = collections.namedtuple('Card', ['rank', 'suit'])
beer_card = Card('7', 'diamonds')
print(beer_card.rank) # 7
print(beer_card.suit) # diamonds

一副纸牌类

class FrenchDeck(object):
    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 rank in self.ranks
                       for suit in self.suits]

    def __len__(self):
        return len(self._cards)

    def __getitem__(self, item):
        return self._cards[item]

deck = FrenchDeck()
print(len(deck)) # 52

索引

第一张或最后一张,deck[0] 或
deck[-1]。这都是由 getitem 方法提供的

print(deck[0]) # Card(rank='2', suit='spades')
print(deck[-1]) # Card(rank='A', suit='hearts')

随机抽一张牌

from random import choice # choice(seq)
'''    def choice(self, seq):
        """Choose a random element from a non-empty sequence."""
            i = self._randbelow(len(seq))
        return seq[i]'''
# 还是调用了__getitem__
print(choice(deck)) # Card(rank='K', suit='clubs')
print(choice(deck)) # Card(rank='7', suit='hearts')

切片

因为 getitem 方法把 [] 操作交给了 self._cards 列表,所以我们的 deck 类自动
支持切片(slicing)操作

print(deck[:2]) # [Card(rank='2', suit='spades'), Card(rank='3', suit='spades'),

迭代

仅仅实现了 getitem 方法,这一摞牌就变成可迭代的了

for card in deck:
    print(card)
# 输出
# Card(rank='2', suit='spades')
# Card(rank='3', suit='spades')
# Card(rank='4', suit='spades')
# ...

还可以反向迭代

for card in reversed(deck): # reversed(sequence) -> reverse iterator
    print(card)

解释器需要迭代对象 x 时,会自动调用 iter(x)。
内置的 iter 函数有以下作用。
1. 检查对象是否实现了 iter 方法,如果实现了就调用它,获取一个迭代器。
2. 如果没有实现 iter 方法,但是实现了 getitem 方法,Python 会创建一个迭
代器,尝试按顺序(从索引 0 开始)获取元素。
3. 如果尝试失败,Python 抛出 TypeError 异常,通常会提示“C object is not iterable”(C
对象不可迭代),其中 C 是目标对象所属的类。
4. 任何 Python 序列都可迭代的原因是,它们都实现了 getitem 方法。其实,标准的序
列也都实现了 iter 方法,因此你也应该这么做。之所以对 getitem 方法做特
殊处理,是为了向后兼容,而未来可能不会再这么做”’

包含关系

迭代通常是隐式的,譬如说一个集合类型没有实现 contains 方法,那么 in 运算符
就会按顺序做一次迭代搜索。于是,in 运算符可以用在我们的 FrenchDeck 类上,因为
它是可迭代的

print(Card('Q', 'hearts') in deck) # True
print(Card('7', 'beasts') in deck) # False

排序

那么排序呢?我们按照常规,用点数来判定扑克牌的大小,2 最小、A 最大;同时还要加
上对花色的判定,黑桃最大、红桃次之、方块再次、梅花最小。下面就是按照这个规则来
给扑克牌排序的函数,梅花 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)
    #print(rank_value)
    print(rank_value*len(suit_values) + suit_values[card.suit])
    return rank_value*len(suit_values) + suit_values[card.suit]

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

如何使用特殊方法

  • 特殊方法的存在是为了被 Python 解释器调用的,你自己并不需要调用它
    们。也就是说没有 my_object.len() 这种写法,而应该使用 len(my_object)。在
    执行 len(my_object) 的时候,如果 my_object 是一个自定义类的对象,那么 Python 会
    自己去调用其中由你实现的 len 方法。
  • 然而如果是 Python 内置的类型,比如列表(list)、字符串(str)、字节序列
    (bytearray)等,那么 CPython 会抄个近路,len 实际上会直接返回
    PyVarObject 里的 ob_size 属性。PyVarObject 是表示内存中长度可变的内置对象的 C
    语言结构体。直接读取这个值比调用一个方法要快很多。
  • 很多时候,特殊方法的调用是隐式的,比如 for i in x: 这个语句,背后其实用的是
    iter(x),而这个函数的背后则是 x.iter() 方法。当然前提是这个方法在 x 中被实
    现了。

模拟数值类型

bool(x) 的背后是调用 x.bool() 的结果;如果不存
bool 方法,那么 bool(x) 会尝试调用 x.len()。若返回 0,则 bool 会返回
False;否则返回 True。

from math import hypot

class Vector(object):

    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

    def __repr__(self):
        print('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, other):
        # 乘法
        return Vector(self.x*other, self.y*other)

为什么len不是普通方法

  • 如果 x 是一个内置类型的实例,那么 len(x) 的速度会非
    常快。背后的原因是 CPython 会直接从一个 C 结构体里读取对象的长度,完全不会调用任
    何方法。获取一个集合中元素的数量是一个很常见的操作,在 str、list、memoryview
    等类型上,这个操作必须高效。
  • 换句话说,len 之所以不是一个普通方法,是为了让 Python 自带的数据结构可以走后门,abs 也是同理。但是多亏了它是特殊方法,我们也可以把 len 用于自定义数据类型。这种处理方式在保持内置类型的效率和保证语言的一致性之间找到了一个平衡点,也
    印证了“Python 之禅”中的另外一句话:“不能让特例特殊到开始破坏既定规则。

猜你喜欢

转载自blog.csdn.net/azsx02/article/details/78523776