Py学习  »  Python

Python小知识:Python 迭代器与生成器

Python技术博文 • 6 年前 • 676 次点击  

迭代器(iterator)与生成器(generator)是 Python 中比较常用又很容易混淆的两个概念,今天就把它们梳理一遍,并举一些常用的例子。

for 语句与可迭代对象(iterable object):

for i in [1, 2, 3]:
   print(i)

#执行结果
1
2
3
obj = {"a": 123, "b": 456}
for k in obj:
   print(k)
#执行结果

b
a

这些可以用在 for 语句进行循环的对象就是可迭代对象。除了内置的数据类型(列表、元组、字符串、字典等)可以通过 for 语句进行迭代,我们也可以自己创建一个容器,包含一系列元素,可以通过 for 语句依次循环取出每一个元素,这种容器就是迭代器(iterator)。除了用 for 遍历,迭代器还可以通过 next() 方法逐一读取下一个元素。要创建一个迭代器有3种方法,其中前两种分别是:

  1. 为容器对象添加 iter() 和 next() 方法;iter() 返回迭代器对象本身 self,next() 则返回每次调用 next() 或迭代时的元素;

  2. 内置函数 iter() 将可迭代对象转化为迭代器

ita = iter([1, 2, 3])
print(type(ita))
print(next(ita))
print(next(ita))
print(next(ita))
print(next(ita))
#执行结果

1
2
3
Traceback (most recent call last):
 File "/usercode/file3.py", line 6, in
   print(next(ita))
StopIteration

#用next()方法来获取容器中的下一个元素,当没有可访问元素后,就抛出StopIteration异常。

注意:Python2和Python3中调用next()方法略有不同,例如在Python2环境下:

def


    
 fib(max):  
   n,a,b=0,0,1  
   while(n        yield b  
       a,b=b,a+b  
       n+=1

g=fib(5)  
g.next()
#执行结果

Traceback (most recent call last):  
 File "", line 1, in  
   f.next()  
AttributeError: 'generator' object has no attribute 'next'

上面的next方法调用错误。在Python3.x中若使用generator的next属性,应该这么调用:

g=fib(4)  
next(g) #与之前的g.next()不同
# Create iterator Object

class Container:
   def __init__(self, start = 0, end = 0):
       self.start = start
       self.end = end
   def __iter__(self):
       print("[LOG] I made this iterator!")
       return self
   def __next__(self):
       print("[LOG] Calling __next__ method!")
       if self.start < self.end:
           i = self.start
           self.start += 1
           return i
       else:
           raise StopIteration()
c = Container(0, 5)
for i in c:
   print(i)

#执行结果

[LOG] I made this iterator!
[LOG] Calling __next__ method!
0
[LOG] Calling __next__ method!
1
[LOG] Calling __next__ method!
2
[LOG] Calling __next__ method!
3
[LOG] Calling __next__ method
!4
[LOG] Calling __next__ method!

创建迭代器对象的好处是当序列长度很大时,可以减少内存消耗,因为每次只需要记录一个值即刻(经常看到人们介绍 Python 2.7 的 range 函数时,建议当长度太大时用 xrange 更快,在 Python 3.5 中已经去除了 xrange 只有一个类似迭代器一样的 range)。

生成器

前面说到创建迭代器有3种方法,其中第三种就是生成器(generator)。生成器函数可以通过常规的def语句来定义,但是不用return返回,而是用yield一次返回一个结果,在每个结果之间挂起和继续它们的状态,来自动实现迭代协议。

也就是说,yield是一个语法糖,内部实现支持了迭代器协议,同时yield内部是一个状态机,维护着挂起和继续的状态。

生成器通过 yield 语句快速生成迭代器,省略了复杂的 iter() & next() 方式。

def container(start, end):
    while start         yield start
        start += 1
c = container(0, 5)
print(type(c))
print(next(c))
next(c)
for i in c:
    print(i)
#执行结果

0
2
3
4

简单来说,yield 语句可以让普通函数变成一个生成器,并且相应的next()方法返回的是yield后面的值。一种更直观的解释是:程序执行到yield会返回值并暂停,再次调用next()时会从上次暂停的地方继续开始执行:

def gen():
   yield 5
   yield "Hello"
   yield "World"
   yield 4

for i in gen():
   print(i)

#执行结果
5
Hello
World
4

生成器表达式

在开始介绍生成器表达式之前,先看看我们比较熟悉的列表解析( List comprehensions),列表解析一般都是下面的形式。

[expr for iter_var in iterable if cond_expr]

迭代iterable里所有内容,每一次迭代后,把iterable里满足cond_expr条件的内容放到iter_var中,再在表达式expr中应该iter_var的内容,最后用表达式的计算值生成一个列表。

例如,生成一个list来保护50以内的所以奇数:

[i for i in range(50) if i%2]

生成器表达式当序列过长, 而每次只需要获取一个元素时,应当考虑使用生成器表达式而不是列表解析。生成器表达式的语法和列表解析一样,只不过生成器表达式是被()括起来的,而不是[],如下:

(expr for iter_var in iterable if cond_expr)

看一个例子:

gen = (i for i in range(50) if i%2)
print(gen)
print([i for i in gen])

#执行结果
at 0x7f7fac1f3f30>
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49]

生成器表达式并不是创建一个列表, 而是返回一个生成器,这个生成器在每次计算出一个条目后,把这个条目”产生”(yield)出来。 生成器表达式使用了”惰性计算”(lazy evaluation),只有在检索时才被赋值(evaluated),所以在列表比较长的情况下使用内存上更有效。

继续看一个例子:




    
gen = (i for i in range(50) if i%2)
print("__iter__" in dir(gen))
print("__next__" in dir(gen))
print(sum(gen))
print([i for i in gen])

#执行结果
True
True
625
[]

从这个例子中可以看到,生成器表达式产生的生成器,它自身是一个可迭代对象,同时也是迭代器本身。

总结

本文介绍了Python迭代器和生成器的相关内容。

  • 通过实现迭代器协议对应的iter()和next()方法,可以自定义迭代器类型。对于可迭代对象,for语句可以通过iter()方法获取迭代器,并且通过next()方法获得容器的下一个元素。

  • 像列表这种序列类型的对象,可迭代对象和迭代器对象是相互独立存在的,在迭代的过程中各个迭代器相互独立;但是,有的可迭代对象本身又是迭代器对象,那么迭代器就没法独立使用。

  • 生成器是一种特殊的迭代器,内部支持了生成器协议,不需要明确定义iter()和next()方法。

  • 生成器通过生成器函数产生,生成器函数可以通过常规的def语句来定义,但是不用return返回,而是用yield一次返回一个结果。



今天看啥 - 高品质阅读平台
本文地址:http://www.jintiankansha.me/t/lLk0zuVh2y
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/3169
 
676 次点击