您的位置:首页 > 编程语言 > Python开发

python·用生成器和迭代器实现自己的xrange

2008-10-13 17:59 681 查看
用过python的朋友一定很熟悉下面这两行代码:
>>> for i in xrange(0,10,1):
print i
上面的两行代码是用一个循环打印0-9这十个数字。你也想实现像xrange这样的可以用在for语句里的函数(类)吗?那跟我来吧!
首先来介绍一下python的yield语句,Yield这个单词本身有产生、产出的意思,它的语法是:
yield 表达式
关于yield语句,官方manual是这样说的:yield语句仅用以定义生成器函数,而且它只能出现在生成器函数内;在函数定义中使用yield语句的充分理由是想实现以个生成器函数而不是普通函数。当生成器函数被调用,它返回一个视作生成器的迭代器的迭代器、更通俗地说是一个生成器。生成器函数的函数体将被生成器的next方法重复调用直到产生一个异常;当yield语句被执行的时候生成器的状态被冻结并且表达式的值返回给next()的调用者,所谓“冻结”我们可以理解成函数在这里被保存现场并切换了出去(如果你了解操作系统的进程管理的话,应该很容易理解这句话)。
嗯,太隐晦了些,看个实例吧。
>>> def simple_xrange (num):
while(num):
yield num
num -= 1

>>> l = list(simple_xrange(8))
>>>print l
[8, 7, 6, 5, 4, 3, 2, 1]
在上例中我们实现了一个简单的xrange,生成倒序的数字系列。但还是看不出这个simple_xrange是怎么执行的,现在我们来看看下面的实验:
>>> it = simple_xrange (8)
>>> it.next()
8
>>> it.next()
7
>>> it.next()
6
……
>>> it.next()
1
>>> it.next()

Traceback (most recent call last):
File "<pyshell#48>", line 1, in -toplevel-
it.next()
StopIteration
现在我们从上面的实验中来看simple_xrange的执行过程:
1、 当执行it = simple_xrange(8)时,simple_xrange返回一个生成器,即it成为一个生成器。
2、 当执行it.next()时,simple_xrange的函数体被执行,当执行到yield num语句时,simple_xrange被“冻结”,然后返回num,即8
3、 再次执行it.next(),simple_xrange“解冻”,执行num -= 1,因为是循环,所以再执行while(num),这时又是执行yield num,simple_xrange被“冻结”,返回num,即7
4、 再一次次调用下去,直到simple_xrange的while(num)不成立,跳出循环,返回时next()函数抛出一个StopIteration异常,这时生成器函数就执行完结了。
把上面的1234条目跟上文python manual的说法对照一下,是相互呼应的,这样我们就理解了xrange的实现机理,从而可以利用yield语句写出自己的xrange了。
理解了yield之后,理解另一种实现xrange的方法就容易多了,这种方法就是定义自己的迭代器。对于迭代器,python manual的说法是这样的:python支持一种超越容器的迭代器观念,使得用户定义的类支持迭代。迭代器对象需要支持__iter__()和next()两个方法,其中__iter__()返回迭代器自身,next()返回系列的下一个元素。嗯,还是通过实例来说吧:
>>> class simple_xrange:
def __init__(self, num):
self._num = num
def __iter__(self):
return self
def next(self):
if self._num <= 0:
raise StopIteration
tmp = self._num
self._num -= 1
return tmp

>>> l = list(simple_xrange(8))
>>> l
[8, 7, 6, 5, 4, 3, 2, 1]
哈哈,读一下源代码,似乎这个比yield语句更简明易懂,也许这就是在有了yield语句之后还要支持迭代器类型的原因吧!有了yield知识,理解这段源代码是很简单的了,我就不多言了。
搞了这么久,实现自己的xrange有必要吗?当然是有的,xrange只是产生了一个系列,如果要对这个系列有什么扩展的话,写出来的代码就比较难看了。举个在现实工作中我遇到的例子:我做一个纸牌游戏,我用list来表示将要打出的牌(我用0~53表示一副牌,其中0表示最小的牌——方块3),如[0,0,3,3]表示两对编号分别为0,3的牌,即由两个方块3两个黑桃3组成的炸弹(本游戏使用两副牌,所以可以有两个相同的牌ID)。后来修改了游戏规则,新的游戏规则规定大joker(牌ID为53)可以变化为任意牌,比如[0,0,3,53]也是一个炸弹。这时我写了下面的代码来判断一个list是不是一个正确的牌型:
#当cards里有big joker时调用本函数判断是否为有效牌型
def is_valid_pattern_with_big_joker(cards):
_cards = cards[:] #因为要改变cards,所以函数内使用cards的拷贝
be_replace_card = 53 #big joker将被替换
#枚举所有的可能
for i in xrange(53, -1,-1):
_cards.remove(be_replace_card)
_cards.append(i)
be_replace_card = i
if is_valid_pattern(_cards)#如果还有big joker,is_valid_pattern会递归调用本函数
return True
return False
看那for循环的循环体,多么复杂,又是remove又是append还有中间变量要保存,有没有办法简单点?有!使用迭代器吧。
class ReplacedBigJokerCards:
def __init__(self, cards):
self._cards = cards[:]
self._be_replaced_card = 53
self._candidate = 52
def __iter__(self):
return self
def next(self):
if self._candidate < 0:
raise StopIteration
self._cards.remove(self._be_replace_card)
self._cards.append(self._candidate)
self._be_replace_card = self._candidate
self._candidate -= 1
return self._cards

def is_valid_pattern_with_big_joker(cards):
for _cards in ReplacedBigJokerCards(cards):
if is_valid_pattern(_cards)
return True
return False

看,现在把大joker变牌的细节隐藏起来,is_valid_pattern_with_big_joker变得优雅多了。重要的另一点就是在游戏中,除了判定牌型外,还有智能提示等多个功能都能够重用ReplaceBigJokerCards,使用这样的定制迭代器,比散落在代码各处的remove/append比好得多。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: