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

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

2008-03-11 14:54 603 查看
[align=center]·python·用生成器和迭代器实现自己的xrange[/align][align=center][/align][align=left]声明:本文由恋花蝶发表于http://blog.csdn.net/lanphaday,版权所有,欢迎转载。转载时应保留声明。谢谢。[/align][align=center] [/align]       用过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 "", 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,即83、  再次执行it.next(),simple_xrange“解冻”,执行num -= 1,因为是循环,所以再执行while(num),这时又是执行yield num,simple_xrange被“冻结”,返回num,即74、  再一次次调用下去,直到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 StopIterationself._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比好得多。 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: