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

news/2024/7/7 21:06:37
·python·用生成器和迭代器实现自己的xrange
声明:本文由恋花蝶发表于 http://blog.csdn.net/lanphaday,版权所有,欢迎转载。转载时应保留声明。谢谢。
 
       用过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,即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比好得多。
 



http://www.niftyadmin.cn/n/3647638.html

相关文章

直面春招!最全MyBatis中XML映射文件标签分析+示例!感悟分享

前言 关于为什么要有分布式锁这个东西&#xff0c;欢迎阅读我的zk分布式锁的实现&#xff0c;介绍了单机高并发、分布式高并发的解决方案&#xff1a; 用ZooKeeper实现分布式锁 这里再切入本例将使用的场景模拟&#xff1a;商品秒杀&#xff0c;或者说高并发下&#xff0c;对…

栈的弹出函数pop()实现方案探讨

我想就上面的你说的3、4两条谈谈。STL的确是没有返回两个参数&#xff0c;甚至没有返回参数。std::stack::pop方法的声明形式是void pop()。也许各个版本有所不同&#xff0c;但SGI STL和STLport是这样的。作为使用最广泛的两种STL实现&#xff0c;我们完全可以认为这是业界认为…

想搞清楚ZooKeepe?助你面试一臂之力,含泪整理面经

接口概述: 接口是Java语言中的一种引用类型&#xff0c;是方法的"集合"&#xff0c;所以接口的内部主要就是定义方法&#xff0c;包含常量,抽象方法&#xff08;JDK 7及以前&#xff09;&#xff0c;额外增加默认方法和静态方法&#xff08;JDK 8&#xff09;,额外增…

从实例重温工厂模式和单件模式

本文由恋花蝶最初发表于&#xff1a;http://blog.csdn.net/lanphaday&#xff0c;你可以在保持文章完整和保留本声明的情况下转帖、分发和印刷等。http://community.csdn.net/Expert/topic/4892/4892570.xml?temp.8727381今天一个好朋友发了上面这个贴&#xff0c;并邀我过去看…

想自学Java的速来!MySQL索引为何选择B+树?复习指南

前言 又到一年金九银十之际。 Java作为目前用户最多&#xff0c;使用范围最广的软件开发技术之一。 Java的技术体系主要由支撑Java程序运行的虚拟机&#xff0c;提供各开发领域接口支持的Java,Java编程语言及许多第三方Jvav框架构成。 其中&#xff0c;以Java的虚拟器为今天的着…

以讹传讹的小故事大道理

本文由恋花蝶最初发表于http://blog.csdn.net/lanphaday&#xff0c;可以转载、印刷等&#xff0c;但不可用于商业用途。并敬请保留本文完整&#xff0c;及包含本声明。昨天在公司内网看到同事贴出下面的这个小故事&#xff0c;终于是忍不住要反驳一下&#xff0c;就写了下次的…

成功入职字节跳动!只用了几百行代码写的百度搜索引擎,先收藏了

前言 MySQL 是最流行的关系型数据库管理系统(RDBMS)之一。 MySQL作为一个关系型数据库管理系统&#xff0c;因为其速度、可靠性和适应性而备受关注。大多数人都认为在不需要事务化处理的情况下&#xff0c;MySQL是管理内容最好的选择。虽然功能未必很强大&#xff0c;但因为它…

基本A*算法python实现

本文由恋花蝶发表于http://blog.csdn.net/lanphaday&#xff0c;可以在保证全文完整的前提下任意形式自由传播&#xff0c;但必须保留本声明&#xff0c;违反必究。最近因为一个任务要用到A*算法&#xff0c;就用C实现了一份。不过只是用A*来检测从A点到B点有无通路&#xff0c…