概述
1.1 一摞Python风格的纸牌
__getitem__和__len__
例1,一摞有序的纸牌
importcollections#namedtuple是一个函数,它用来创建一个自定义的tuple对象,并且规定了tuple元素,#并可以用属性而不是索引来引用tuple的某个元素。#namedtuple用来构建只有少数属性但是没有方法的对象
Card = collections.namedtuple('Card',['rank','suit'])classFrenchDeck:
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 inself.ranksfor suit inself.suits]def __len__(self):returnlen(self._cards)def __getitem__(self, position):returnself._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')52Card(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 importFrenchDeck, 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 importchoice>>>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)defspades_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')
...(46cards 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 importhypotclassVector:def __init__(self, x=0, y=0):
self.x=x
self.y=ydef __repr__(self):return 'Vector(%r, %r)' %(self.x, self.y)def __abs__(self):returnhypot(self.x, self.y)def __bool__(self):returnbool(abs(self))def __add__(self, other):
x= self.x +other.x
y= self.y +other.yreturnVector(x,y)def __mul__(self, scalar):return Vector(self.x * scalar,self.y * scalar)
为了给这个类设计API,我们先写个模拟的控制台会话来做doctest。
>>> from vector importVector>>> v1 = Vector(2,4)>>> v2 = Vector(2,1)>>> v1 +v2
Vector(4, 5)>>> v = Vector(3,4)>>>abs(v)5.0
>>> v * 3Vector(9, 12)>>> abs(v * 3)15.
>>> v * -3
Vector(-9, -12)
abs是一个内置函数,如果输入是整数或者浮点数,它返回的是输入值的绝对值;如果输入是复数,那么返回这个复数的摸。
*运算符实现向量的标量乘法(即向量与数的乘法,得到的结果向量的方向与原向量方向一致,模变大。如果向量与负数相乘,得到的结果向量的方向与原向量相反。)
1.2.2 字符串表示形式
Python有一个内置的函数叫repr,它能把对一个对象用字符串的形式表达出来以便辨认,这就是“字符串表示形式”。
%和str.format是两种格式化字符串的手段。
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 _ , _ 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 _ <>, _ adn _ &, _ or _
反向位运算符
_ rlshift _ , _ rrshift _ , _ rand _ , _ rxor _ , _ ror _
增量赋值位运算符
_ ilshift _ , _ irshift _ , _ iand _ , _ ixor _ , _ ior _
提示:当交换两个操作数的位置时,就会调用反向运算符(b*a而不是a*b)
最后
以上就是幽默热狗为你收集整理的流畅的python第一章_《流畅的Python》学习笔记1(第1章:Python数据模型)的全部内容,希望文章能够帮你解决流畅的python第一章_《流畅的Python》学习笔记1(第1章:Python数据模型)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复