《流畅的Python》学习笔记1(第1章:Python数据模型)

1.1 一摞Python风格的纸牌

__getitem____len__

例1,一摞有序的纸牌

import collections

# namedtuple是一个函数,它用来创建一个自定义的tuple对象,并且规定了tuple元素,
# 并可以用属性而不是索引来引用tuple的某个元素。
# 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 rank in self.ranks
                                       for suit in self.suits]
    def __len__(self):
        return len(self._cards)
    def __getitem__(self, position):
        return self._cards[position]
# 利用namedtuple会轻松得到一个纸牌对象 beer_card = Card('7','diamonds') print(beer_card) # 用len()函数来查看一叠牌有多少张 deck = FrenchDeck() print(len(deck)) # 利用__getitem__方法从一叠牌中抽取特定的一张纸牌,比如第一张或最后一张。 print(deck[0]) print(deck[-1]) # Python内置从一直序列中随机选出一个元素的函数random.choice。 from random import choice print("随机抽取一张牌3次:") print(choice(deck)) print(choice(deck)) print(choice(deck)) # 查看一摞牌最上面3张和只看牌面是A的牌的操作 # 其中第二种操作的具体方法是,先抽出索引12的那张牌,然后每向后数13张牌拿一张 print("最上面3张牌:") print(deck[:3]) print("牌面为A的牌:") print(deck[12::13])

输出结果:

Card(rank='7', suit='diamonds')
52
Card(rank='2', suit='spades')
Card(rank='A', suit='hearts')
随机抽取一张牌3次:
Card(rank='9', suit='clubs')
Card(rank='J', suit='clubs')
Card(rank='10', suit='diamonds')
最上面3张牌:
[Card(rank='2', suit='spades'), Card(rank='2', suit='diamonds'), Card(rank='2', suit='clubs')]
牌面为A的牌:
[Card(rank='5', suit='spades'), Card(rank='8', suit='diamonds'), Card(rank='J', suit='clubs'), Card(rank='A', suit='hearts')]

为了能够清晰的看出每一步得到的结果,也不需要这么多的输出语句,(注意注释掉上面暗调的代码)可在控制台运行:

>>> from frenchdeck import FrenchDeck, Card
>>> beer_card = Card('7','diamonds')
>>> beer_card
Card(rank='7', suit='diamonds')
>>> deck = FrenchDeck()
>>> len(deck)
52
>>> deck[0]
Card(rank='2', suit='spades')
>>> deck[-1]
Card(rank='A', suit='hearts')
>>> from random import choice
>>> choice(deck)
Card(rank='2', suit='hearts')
>>> choice(deck)
Card(rank='K', suit='hearts')
>>> choice(deck)
Card(rank='J', suit='spades')
>>> deck[:3]
[Card(rank='2', suit='spades'), Card(rank='2', suit='diamonds'), Card(rank='2', suit='clubs')]
>>> deck[12::13]
[Card(rank='5', suit='spades'), Card(rank='8', suit='diamonds'), Card(rank='J', suit='clubs'), Card(rank='A', suit='hearts')]
>>>

给一摞扑克牌排序,按照2最小,A最大;黑桃最大,红桃次之,方块再次,梅花最小。

按照这个规则给扑克牌排序,梅花2大小是0,黑桃A是51

suit_values = dict(spades =3, hearts=2, diamonds=1, clubs=0)
def spades_high(card):
    # 在[2,3,4,5,6,7,8,9,10,J,Q,K,A]中的位置
    rank_value = FrenchDeck.ranks.index(card.rank)
    # suit_values[card.suit]加上花色的大小
    return rank_value*len(suit_values)+suit_values[card.suit]
deck = FrenchDeck()
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')

一般注记:按照目前的设计,FenchDeck是不能洗牌的,因为这摞牌是不可变的:卡牌和它们的位置都是固定的,除非破坏这个类的封装性,直接对_cards进行操作。第11章会讲到。

1.2 如何使用特殊方法

首先明确一点,特殊方法的存在是为了被Python解释器调用的,你自己并不需要调用它们。也就是说没有my_object.__len__()这种写法,而应该使用len(my_object)。

通常你的代码无需直接使用特殊方法。除非有大量的元编程存在,直接调用特殊方法的频率远远低于你去实现它们的次数。唯一的例外可能是__init__方法。

1.2.1 模拟数值类型

我们来实现一个二维向量(vector)类,这里的向量就是欧几里得几何中常用的概念。

__repr__、__abs__、__add__和__mul__

例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)
    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)

为了给这个类设计API,我们先写个模拟的控制台会话来做doctest。

>>> from vector import Vector
>>> 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.
>>> v * -3
Vector(-9, -12)

abs是一个内置函数,如果输入是整数或者浮点数,它返回的是输入值的绝对值;如果输入是复数,那么返回这个复数的摸。

*运算符实现向量的标量乘法(即向量与数的乘法,得到的结果向量的方向与原向量方向一致,模变大。如果向量与负数相乘,得到的结果向量的方向与原向量相反。)

1.2.2 字符串表示形式

Python有一个内置的函数叫repr,它能把对一个对象用字符串的形式表达出来以便辨认,这就是“字符串表示形式”。

%和str.format是两种格式化字符串的手段。

__str__和__repr__的区别(https://stackoverflow.com/questions/1436703/difference-between-str-and-repr

1.2.3 算术运算符

通过例2发现,__add__和__mul__两个方法的返回值都是新创建的向量对象,被操作的两个向量(self或other)还是原封不动,代码里只是读取了它们的值而已。中缀运算符的基本原则就是不改变操作对象,而是产出一个新的值。

1.2.4 自定义的布尔值

如果想让Vector.__bool__更高效,可以采用:

    def __bool__(self):
        return bool(self.x or self.y)

它不那么易读,却能省掉从abs到__abs__到平方再到平方根这些中间步骤。通过bool把返回类型显示转换为布尔值是为了符合__bool__对返回值的规定,因为or运算符可能会返回x或者y本身的值:若x的值等价于真,则or返回x的值;否则返回y的值。

1.3 特殊方法一览

表1-1:跟运算符无关的特殊方法

类别 方法名
字符串字节序列表示形式 _ repr _ , _ format _ , _ str _ , _ bytes _
数值转换 _ abs _ , _ bool _ , _ complex _ , _ int _ ,_ float _ , _ hash _ , _ index _
集合模拟 _ len _ , _ getitem _ , _ setitem _ , _ delitem _ ,_ contains _
迭代枚举  iter _ , _ reversed _ , _ next _
可调用模拟 _ call _
上下文管理 _ enter _ , _ exit _
实例创建和销毁 _ new _ , _ init _ , _ del _
属性管理 _ getattr _ , _ getattribute _ , _ setattr _ , _ delattr _ , _ dir _
属性描述符 _ get _ , _ set _ , _ delete _
跟踪相关的服务 _ prepare _ , _ instancecheck _ , _ subclasscheck _

表1-2:跟运算符相关的特殊方法

类别 方法名和对应的运算符
一元运算符  neg _ -, _ pos _ +, _ abs _ abs( )
比较运算符 _ lt _ <, _ le _ <=, _ eq _ ==, _ ne _ !=, _ gt _ > , _ ge _ >=,
算术运算符 _ add _ + , _ sub _ - , _ mul _ * , _ truediv _ / , _ floordiv _ // 
_ mod _ % , _ divmod _ divmode() , _ pow _ **或pow() _ round _ round()
反向算数运算符  _ radd _ , _ rsub _ , _ rmul _ , _ rtruediv _ , _ rfloordiv _ 
_ rmod _ , _ rdivmod _ , _ rpow _
增量赋值运算符 _ iadd _ , _ isub _ , _ imul _ , _ itruediv _ , _ ifloordiv _ 
_ imod _ , _ idivmod _ , _ ipow _
位运算符 _ invert _ ~, _ lshift _ <<, _ rshift _ >>, _ adn _ &, _ or _
反向位运算符 _ rlshift _ , _ rrshift _ , _ rand _ , _ rxor _ , _ ror _
增量赋值位运算符 _ ilshift _ , _ irshift _ , _ iand _ , _ ixor _ , _ ior _

提示:当交换两个操作数的位置时,就会调用反向运算符(b*a而不是a*b)

原文地址:https://www.cnblogs.com/cathycheng/p/11353224.html