如何更好地理解Python迭代器和生成器
2015-12-03 · 知道合伙人教育行家
迭代器
迭代器(iterator)是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址。迭代器修改了常规指针的接口,所谓迭代器是一种概念上的抽象:那些行为上像迭代器的东西都可以叫做迭代器。然而迭代器有很多不同的能力,它可以把抽象容器和通用算法有机的统一起来。
迭代器提供一些基本操作符:*、++、==、!=、=。这些操作和C/C++“操作array元素”时的指针接口一致。不同之处在于,迭代器是个所谓的复杂的指针,具有遍历复杂数据结构的能力。其下层运行机制取决于其所遍历的数据结构。因此,每一种容器型别都必须提供自己的迭代器。事实上每一种容器都将其迭代器以嵌套的方式定义于内部。因此各种迭代器的接口相同,型号却不同。这直接导出了泛型程序设计的概念:所有操作行为都使用相同接口,虽然它们的型别不同。
迭代器使开发人员能够在类或结构中支持foreach迭代,而不必整个实现IEnumerable或者IEnumerator接口。只需提供一个迭代器,即可遍历类中的数据结构。当编译器检测到迭代器时,将自动生成IEnumerable接口或者IEnumerator接口的Current,MoveNext和Dispose方法。
生成器
生成器是一次生成一个值的特殊类型函数。可以将其视为可恢复函数。调用该函数将返回一个可用于生成连续 x 值的生成器【Generator】
简单的说就是在函数的执行过程中,yield语句会把你需要的值返回给调用生成器的地方,然后退出函数,下一次调用生成器函数的时候又从上次中断的地方开始执行,而生成器内的所有变量参数都会被保存下来供下一次使用。
当一个txt文件有几万行的时候,你是用cat ,会发现屏幕不停的刷不停的刷,影响你正常阅读。
这时候你需要使用more来一页一页阅读。
同样的道理,当文件被读入到内存时候,如果数据太大,会导致内存被占用过多。这时候需要一个向more的功能一次读取一点,这个就是迭代器的功能。
那么怎么样才能有这样的功能存在呢?这就是生成器的作用。让cat aa.txt通过生成器变成more aa.txt的效果。
1. __init__方法
Python 类中有默认的构造函数__init__我们可以覆盖它来试试。如下:
class FooBar:
def __init__(self): self.somevar=42 f=FooBar() print f.somevar
我们修改一下如下:
class FooBar:
def __init__(self,value=42): self.somevar=value f=FooBar("what's this?")
print f.somevar
输出如下:
what's this?
2. 重写方法
如果一个方法在B类的一个实例中被调用,但在B类中没有找到方法,那么就会在超类A里面找。 如下所示:
class A:
def hello(self):
print "hello ,I.m A" class B(A): pass
a=A() b=B() a.hello() b.hello()
输出如下:
hello ,I.m A hello ,I.m A
B类没有定义自己的方法hello调用的是父类的hello方法。 如果进行重写这个方法,如下:
class A:
def hello(self):
print "hello ,I.m A" class B(A): pass
def hello(self): print "Hello,I'm B" a=A() b=B() a.hello() b.hello()
输出如下:
hello ,I.m A Hello,I'm B
3. 使用Super函数
我看来看下个例子如下:
class Bird:
def __init__(self): self.hungry=True def eat(self): if self.hungry: print 'Aaah...' self.hungry=False else:
print "No,thansk"
class SongBird(Bird): def __init__(self): self.sound='Squawk!'
def sing(self): print self.sound
sb=SongBird() sb.sing() sb.eat()
运行如下:
Squawk!
Traceback (most recent call last):
AttributeError: SongBird instance has no attribute 'hungry'
没有hungry属性。
没有得到父类的属性,需要用到Super函数,处理后如下:
from _pyio import __metaclass__ __metaclass__=type class Bird:
def __init__(self): self.hungry=True def eat(self): if self.hungry: print 'Aaah...' self.hungry=False else:
print "No,thansk"
class SongBird(Bird): def __init__(self): # Bird.__init__(self)
super(SongBird,self).__init__() self.sound='Squawk!' def sing(self): print self.sound
sb=SongBird() sb.sing() sb.eat()
运行如下:
Squawk! Aaah...
迭代器是访问集合元素的一种方式。迭代器对象从集合的第一个元素开始访问,知道所有的元素被访问完结束。迭代器只能往前不会后退,不过这也没什么,因为人们很少在迭代途中往后退。
使用迭代器的优点
对于原生支持随机访问的数据结构(如tuple、list),迭代器和经典for循环的索引访问相比并无优势,反而丢失了索引值(可以使用内建函数enumerate()找回这个索引值)。但对于无法随机访问的数据结构(比如set)而言,迭代器是唯一的访问元素的方式。
另外,迭代器的一大优点是不要求事先准备好整个迭代过程中所有的元素。迭代器仅仅在迭代到某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁。这个特点使得它特别适合用于遍历一些巨大的或是无限的集合,比如几个G的文件,或是斐波那契数列等等。
迭代器更大的功劳是提供了一个统一的访问集合的接口,只要定义了__iter__()方法对象,就可以使用迭代器访问。
迭代器有两个基本的方法
next方法:返回迭代器的下一个元素
__iter__方法:返回迭代器对象本身
简单地讲,yield 的作用就是把一个函数变成一个 generator,带有 yield 的函数不是一个普通函数,Python 解释器会将其视为一个 generator,调用 fab(5) 不会执行fab 函数,而是返回一个 iterable 对象!在 for 循环执行时,每次循环都会执行 fab 函数内部的代码,执行到 yield b 时,fab 函数就返回一个迭代值,下次迭代时,代码从 yield b 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。
另外,迭代器的一大优点是不要求事先准备好整个迭代过程中所有的元素。迭代器仅仅在迭代到某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁。这个特点使得它特别适合用于遍历一些巨大的或是无限的集合,比如几个G的文件,或是斐波那契数列等等。
迭代器更大的功劳是提供了一个统一的访问集合的接口,只要定义了__iter__()方法对象,就可以使用迭代器访问。