纸牌计算-巧妙的利用类
基础知识 内建模块 collections
collections是Python内建的一个集合模块,提供了许多有用的集合类。
namedtuple
我们知道tuple可以表示不变集合,例如,一个点的二维坐标就可以表示成:
>>> p = (1, 2)
但是,看到(1, 2),很难看出这个tuple是用来表示一个坐标的。
定义一个class又小题大做了,这时,namedtuple就派上了用场:
>>> from collections import namedtuple
>>> Point = namedtuple('Point', ['x', 'y'])
>>> p = Point(1, 2)
>>> p.x
1
>>> p.y
2
namedtuple
是一个函数,它用来创建一个自定义的tuple
对象,并且规定了tuple元素的个数,并可以用属性而不是索引来引用tuple
的某个元素。
这样一来,我们用namedtuple
可以很方便地定义一种数据类型,它具备tuple
的不变性,又可以根据属性来引用,使用十分方便。
可以验证创建的Point对象是tuple的一种子类:
>>> isinstance(p, Point)
True
>>> isinstance(p, tuple)
True
类似的,如果要用坐标和半径表示一个圆,也可以用namedtuple定义:
# namedtuple('名称', [属性list]):
Circle = namedtuple('Circle', ['x', 'y', 'r'])
deque
使用list存储数据时,按索引访问元素很快,但是插入和删除元素就很慢了,因为list是线性存储,数据量大的时候,插入和删除效率很低。
deque是为了高效实现插入和删除操作的双向列表,适合用于队列和栈:
>>> from collections import deque
>>> q = deque(['a', 'b', 'c'])
>>> q.append('x')
>>> q.appendleft('y')
>>> q
deque(['y', 'a', 'b', 'c', 'x'])
deque
除了实现list的append()
和pop()
外,还支持appendleft()
和popleft()
,这样就可以非常高效地往头部添加或删除元素。
除此之外,在collections里还包含以下常用的模块,如:
defaultdict(用于dict,当key不存在时,能返回一个默认值,而不是报错)
OrderedDict(如果要保持Key的顺序,可以用OrderedDict)
Counter(是一个简单的计数器,例如,统计字符出现的个数)
设计一副扑克牌
# 1.1 python风格的纸牌
import collections
from random import choice # 内置的随机选元素模块
Card = collections.namedtuple('Card', ['rank', 'suit']) # 一个元组(点数,花色)
class FrenchDeck:
ranks = [str(n) for n in range(2,11) ] + list('JQKA') # list('123')结果就是['1','2','3'],本质上为2个list相加
suits = 'spades diamonds clubs hearts'.split() # 若split('s') 结果 ['', 'pade', ' diamond', ' club', ' heart', '']
# ranks从2~A,suits:红桃,红方,黑方,黑花
def __init__(self): # __init__ 用法值得关注,类实例的值就是cardss,实例的属性
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, position):
return self._cards[position]
# 有了 spades_high 函数, 就能对这摞牌进行升序排序了
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]
deck = FrenchDeck()
print( len(deck) ) # 结果52;上方的self._cards在for in 中选,4*(9+4)=52种组合
print( deck.__getitem__(0) ) # 结果:Card(rank='2', suit='spades')
print( deck[0] ) # 结果:同上,红桃2是第一张牌
print( choice(deck) ) # 表示随机选牌,如 Card(rank='3', suit='clubs')
# 查看一副牌最上面的3张
print( deck[:3] )
# >>>[Card(rank='2', suit='spades'), Card(rank='3', suit='spades'), Card(rank='4', suit='spades')]
# 从第12张牌开始拿,后面每隔13抽一张,开头有个0,实际就是隔13抽一张,结果抽了4个A
print( deck[12::13] ) # <class '__main__.FrenchDeck'>;该类无须外部输入参数,封装好,自己调用自己的参数就可以实现模拟扑克,高手
# >>> [Card(rank='A', suit='spades'), Card(rank='A', suit='diamonds'), Card(rank='A', suit='clubs'), Card(rank='A', suit='hearts')]
print(type(FrenchDeck()))
# 按花色排序,打印出所有的牌
for card in deck:
print(card)
# 对牌进行升序排序
for card in sorted(deck, key = spades_high):
print(card)
# >>> 结果是2,2,2,2,3,3,3,3...这样排序下去的
虽然 FrenchDeck 隐式地继承了 object 类, 但功能却不是继承而来的。 我们通过数据模型和一些合成来实现这些功能。 通过实现 len和__getitem__
这两个特殊方法, FrenchDeck 就跟一个 Python 自有的序列数据类型一样, 可以体现出 Python 的核心语言特性( 例如迭代和切片) 。 同时这个类还可以用于标准库中诸如random.choice
、 reversed 和 sorted 这些函数。 另外, 对合成的运用使得__len__
和 __getitem__
的具体实现可以代理给 self._cards
这个 Python 列表( 即 list 对象)
python中的find、rfind、index、rindex
find()
从左向右寻找子序列的位置,如存在多个相同子序列只返回第一个查找到的位置,如果子序列不存在返回-1
rfind()
从右向左寻找子序列的位置.....
index()
从左向右寻找子序列的位置,如果子序列不存在报错,所以一般我们用find()
更好一些
rindex()
从右向左寻找子序列的位置.....
a = "hello world"
a1 = a.find("l")
a2 = a.rfind("v")
print(a1)
print(a2)
# 输出结果:
2
-1
b = "hello world"
b1 = b.index("l")
b2 = b.rindex("v")
b3 = b.index('e')
print(b1)
print(b2)
print(b3)
# 输出结果:
Traceback (most recent call last):
File "lianxi.py", line 3, in <module>
b2 = b.index("v")
ValueError: substring not found
1
Python中__init__
方法注意点
注意1、__init__
并不相当于C#中的构造函数,执行它的时候,实例已构造出来了。
class A(object):
def __init__(self,name):
self.name=name
def getName(self):
return 'A '+self.name
当我们执行
a=A('hello')
时,可以理解为
a=object.__new__(A)
A.__init__(a,'hello')
即__init__
作用是初始化已实例化后的对象。
注意2、子类可以不重写__init__
,实例化子类时,会自动调用超类中已定义的__init__
class B(A): # 🐱B继承了A后,虽然类B表面上看无须传参,实际上必须传参,因为继承了A类
def getName(self):
return 'B '+self.name
if __name__=='__main__':
b=B('hello')
print b.getName()
# 打印结果: B hello
但如果重写了__init__
,实例化子类时,则不会隐式的再去调用超类中已定义的__init__
class C(A):
def __init__(self):
pass
def getName(self):
return 'C '+self.name
if __name__=='__main__':
c=C()
print c.getName()
则会报"AttributeError: 'C' object has no attribute 'name'”
错误,所以如果重写了__init__
,为了能使用或扩展超类中的行为,最好显式的调用超类的__init__
方法
class C(A):
def __init__(self,name):
super(C,self).__init__(name)
def getName(self):
return 'C '+self.name
if __name__=='__main__':
c=C('hello')
print c.getName()
网友评论