首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

详解解 Python 迭代对象、迭代器、生成器

迭代器

按照GOF(四人帮GANG OF FOUR)的定义,迭代器提供一种方法访问一个容器对象中的各个元素,而又不需要暴露该对象的内部细节。

Python中,迭代器一个定义了__iter__()和__next__()方法的类的实例对象。__iter__()返回一个迭代器,__next__()返回序列的下一个值。推导式和生成器仅仅是迭代器的简单形式。

>>>it1 =iter([1,2,3])#产生一个列表型('list_iterator')的迭代器实例

>>>type(it1)#来看看it1的类型

#列表迭代器,是一个类实例

#list_iterator的一个实例对象

>>>help(it1)

Help on list_iterator object:

classlist_iterator(object)#list_iterator是一个类

| Methods defined here:

|

| __getattribute__(self, name, /)

| Return getattr(self, name).

|

|__iter__(self, /)

| Implement iter(self).

|

| __length_hint__(...)

| Private method returning an estimate of len(list(it)).

|

|__next__(self, /)

| Implement next(self).

|

| __reduce__(...)

| Return state information for pickling.

|

| __setstate__(...)

| Set state information for unpickling.

例2,实现一个斐波那契迭代器Fibonacci Iterator

>>>classFib:#(1)

def__init__(self,max):#(2)

self.a = 0

self.b = 1

self.max = max

def__iter__(self):#(3)

returnself

def__next__(self):#(4)

fib = self.a

iffib >self.max:

raiseStopIteration #(5)

self.a,self.b = self.b,self.a + self.b

returnfib#(6)

>>>foriinFib(100):#(7)

print(i,end =' ')

0 1 1 2 3 5 8 13 21 34 55 89

(1),从头开始创建一个迭代器,Fib必须是个类,而不是个函数

(2),调用Fib(max)实际上是创建一个类Fib的实例,并且使用max来调用 __init__()方法。__init__()方法将max值存为一个实例变量,以便稍后引用。

(3), 当调用iter(fib)时,实际上是调用的__iter__()。(for循环可以自动调用它, 当然也可以手动调用它)。__iter__()可以返回实现__next__()方法所需要的任何对象,大多数情况下,它返回的是自身,因为这个类实现的是自己的__next__()方法。

(4),当将next()方法作用于迭代器,实际上调用的是__next__()方法。

(5), 当__next__()方法抛出一个StopIteration异常,这表明迭代已经耗尽,不象其它异常,这里并没发生错误,只意谓着迭代器再也不能产生新值了。如果调用者是一个foo循环,它会留意StopIteration异常,并优雅地退出循环 (换句话说,它把异常吞掉了),这点魔法是把迭代器用于for循环的关键之处。

(6), __next__()方法简单地用return语句返回,就产生了下一个值。此处不能用yield(这是专用于生成器的语法糖)。

(7), Fib(100)返回一个Fib类的实例,暂且称作fib_inst。

for循环调用iter(fib_inst),返回一个迭代器对象,称作fib_iter。这里,fib_iter== fib_inst,因为__iter__()方法返回自身。但for循环并不知道或者说并不关心这点。

为了遍历迭代器,for循环调用next(fib_iter),也就是调用对象fib_iter的__next__()方法,计算斐波那契数的下一个值并返回,for循环取出此值并赋给i,然后在循环体里对i执行打印操作。

for循环如何知道何时退出?当__next__()方法抛出异常StopIteration exception

的时候,for循环吞掉了异常并优雅地退出了,(但其它类型的异常则正常抛出)。当然你在__next__()方法会看到异常StopIteration exception。

for循环解析

foriinobject:

do_work(i)

#等同于

object_iter = iter(object)

whiletrue:

try:

i =next(object_iter)

do_work(i)

exceptStopIteration:

break

注:__init__,__iter__,__next__,前后都带双下划线的方法有些特殊,它们不能被直接调用。 iter(f)实际调用f.__iter__(),next(f)实际调用f.__next__().

A Plural Rule Iterator

classLazyRules:

rules_filename ='plural6-rules.txt'

def__init__(self):

self.pattern_file =open(self.rules_filename, encoding ='utf-8')

self.cache = []

def__iter__(self):

self.cache_index = 0

returnself

def__next__(self):

self.cache_index += 1

iflen(self.cache) >= self.cache_index:

return self.cache[self.cache_index - 1]

ifself.pattern_file.closed:

raiseStopIteration

line = self.pattern_file.readline()

if notline:

self.pattern_file.close()

raiseStopIteration

pattern, search, replace = line.split(None, 3)

funcs = build_match_and_apply_functions(

pattern, search, replace)

self.cache.append(funcs)

returnfuncs

rules =LazyRules()

(将以上代码存为文件plural6.py,然后再另存为plural6-rules.txt,后面有用)

这个类实现了 __iter__()和 __next__(),所以它可用作一个迭代器。在入口处,实例化这个类并赋给rules。

下面分段讲解:

classLazyRules:

rules_filename ='plural6-rules.txt'

def__init__(self):

self.pattern_file =open(self.rules_filename, encoding ='utf-8')#(1)

self.cache = []#(2)

(1), 当你实例化类LazyRules时,打开样板文件,但是并没有读取它。

(2), 用空列表初始化cache,后面在__next__()中,读取样板文件的行时使用它。

继续之前,先来看看rules_filename,它并没有在方法__iter__()中定义,事实上,它在任何方法中都没被定义,它是一个类级别的定义,是一个类变量,尽管你也可以象实例变量一样访问它(self.rules_filename),它被类LazyRules的所有实例共享。

>>>import plural6

>>>r1 = plural6.LazyRules()

>>>r2 = plural6.LazyRules()

>>>r1.rules_filename#(1)

'plural6-rules.txt'

>>>r2.rules_filename

'plural6-rules.txt'

>>>r2.rules_filename ='r2-override.txt'#(2)

>>>r2.rules_filename

'r2-override.txt'

>>>r1.rules_filename

'plural6-rules.txt'

>>>r2.__class__.rules_filename#(3)

'plural6-rules.txt'

>>>r2.__class__.rules_filename ='papayawhip.txt'#(4)

>>>r1.rules_filename

'papayawhip.txt'

>>>r2.rules_filename#(5)

'r2-override.txt'

(1), 每一个类的实例继承了属性rules_filename及其值。

(2), 改变一个实例的属性值,并不影响另一个实例的属性。

(3), 现在改变类的属性。你可以使用特殊的__class__属性访问类本身属性(而不是个体实例属性)。

(4), 改变了类属性,所有继承自它的实例(目前只有r1了)也受影响。

(5), 那些被重新值覆盖的实例(r2)的属性不受影响。

关于迭代,可迭代对象比较好理解,迭代器就难以理解。好多时候有点只可意会,不能言传了。

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180511G27IH200?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券