Python迭代器,生成器分享

Py西游攻关之迭代器&生成器

一 概要

在了解Python的数据结构时,容器(container)、可迭代对象(iterable)、迭代器(iterator)、生成器(generator)、列表/集合/字典推导式(list,set,dict comprehension)众多概念参杂在一块儿,不免让初学者一头雾水,我将用一篇文章试图将这些概念以及它们之间的关系捋清楚python

图片

 

二 容器(container)

容器是一种把多个元素组织在一块儿的数据结构,容器中的元素能够逐个地迭代获取,能够用 in , not in 关键字判断元素是否包含在容器中。一般这类数据结构把全部的元素存储在内存中(也有一些特列并非全部的元素都放在内存)在Python中,常见的容器对象有:数据结构

  • list, deque, ....app

  • set, frozensets, ....socket

  • dict, defaultdict, OrderedDict, Counter, ....ide

  • tuple, namedtuple, …函数

  • str学习

容器比较容易理解,由于你就能够把它看做是一个盒子、一栋房子、一个柜子,里面能够塞任何东西。从技术角度来讲,当它能够用来询问某个元素是否包含在其中时,那么这个对象就能够认为是一个容器,好比 list,set,tuples都是容器对象:spa

图片

尽管绝大多数容器都提供了某种方式来获取其中的每个元素,但这并非容器自己提供的能力,而是 可迭代对象 赋予了容器这种能力,固然并非全部的容器都是可迭代的。code

 

三 可迭代对象(iterable)

若是给定一个list或tuple,咱们能够经过for循环来遍历这个list或tuple,这种遍历咱们称为迭代(Iteration)。orm

刚才说过,不少容器都是可迭代对象,此外还有更多的对象一样也是可迭代对象,好比处于打开状态的files,sockets等等。但凡是能够返回一个 迭代器 的对象均可称之为可迭代对象,听起来可能有点困惑,不要紧,可迭代对象与迭代器有一个很是重要的区别。先看一个例子:

图片

这里 x 是一个可迭代对象,可迭代对象和容器同样是一种通俗的叫法,并非指某种具体的数据类型,list是可迭代对象,dict是可迭代对象,set也是可迭代对象。 y 和 z 是两个独立的迭代器,迭代器内部持有一个状态,该状态用于记录当前迭代所在的位置,以方便下次迭代的时候获取正确的元素。迭代器有一种具体的迭代器类型,好比 list_iterator , set_iterator 。可迭代对象实现了 __iter__ 和 __next__ 方法(python2中是 next 方法,python3是 __next__ 方法),这两个方法对应内置函数 iter() 和 next() 。 __iter__ 方法返回可迭代对象自己,这使得他既是一个可迭代对象同时也是一个迭代器。

四 迭代器(iterator)

那么什么迭代器呢?它是一个带状态的对象,他能在你调用 next() 方法的时候返回容器中的下一个值,任何实现了 __next__() (python2中实现 next() )方法的对象都是迭代器,至于它是如何实现的这并不重要。

如今咱们就以斐波那契数列()为例,学习为什么建立以及如何建立一个迭代器:

著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数均可由前两个数相加获得:

1, 1, 2, 3, 5, 8, 13, 21, 34, ...

def fab(max):

= 0, 0, 1 
     n <= b, a += n + 1

直接在函数fab(max)中用print打印会致使函数的可复用性变差,由于fab返回None。其余函数没法得到fab函数返回的数列。

== 0, 0, 1 
     n <= b, a += n + 1
     L

代码2知足了可复用性的需求,可是占用了内存空间,最好不要。

对比for i in range(1000): pass和for i in xrange(1000): pass,前一个返回1000个元素的列表,然后一个在每次迭代中返回一个元素,所以可使用迭代器来解决复用可占空间的问题


 == 0, 0, 1 

      self.n <== self.b, self.a += self.n + 1

Fabs 类经过 next() 不断返回数列的下一个数,内存占用始终为常数

Fib既是一个可迭代对象(由于它实现了 __iter__ 方法),又是一个迭代器(由于实现了 __next__ 方法)。实例变量 self .a 和 self.b 用户维护迭代器内部的状态。每次调用 next() 方法的时候作两件事:

  1. 为下一次调用 next() 方法修改状态

  2. 为当前此次调用生成返回结果

迭代器就像一个懒加载的工厂,等到有人须要的时候才给它生成值返回,没调用的时候就处于休眠状态等待下一次调用。

五 for i in (iterable)的内部实现

在大多数状况下,咱们不会一次次调用next方法去取值,而是经过 for i in (iterable),

       

注意:in后面的对象若是是一个迭代器,内部由于有iter方法才能够进行操做,因此,迭代器协议里面有iter和next两个方法,不然for语句没法应用。

 

注意:

 

1

2

for in range10):

        print i     :定时垃圾回收机制:没有引用指向这个对象,则被回收

 

六 生成器(generator)

      生成器算得上是Python语言中最吸引人的特性之一,生成器实际上是一种特殊的迭代器,不过这种迭代器更加优雅。代码3远没有代码1简洁,生成器(yield)既能够保持代码1的简洁性,又能够保持代码3的效果。它不须要再像上面的类同样写 __iter__() 和 __next__() 方法了,只须要一个 yiled 关键字。 生成器有以下特征是它必定也是迭代器(反之不成立),所以任何生成器也是以一种懒加载的模式生成值。用生成器来实现斐波那契数列的例子是:

= 0, 0, 1
     n <= b, a += n + 1


>>>  n  fab(51
1
2
3
5


     fib 就是一个普通的python函数,它特需的地方在于函数体中没有 return 关键字,函数的返回值是一个生成器对象。当执行 f=fib(5) 返回的是一个生成器对象,此时函数体中的代码并不会执行,只有显示或隐示地调用next的时候才会真正执行里面的代码。

     yield 的做用就是把一个函数变成一个 generator,带有 yield 的函数再也不是一个普通函数,Python 解释器会将其视为一个 generator,在 for 循环执行时,每次循环都会执行 fab 函数内部的代码,执行到 yield b 时,fab 函数就返回一个迭代值,下次迭代时,代码从 yield b 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是彻底同样的,因而函数继续执行,直到再次遇到 yield。看起来就好像一个函数在正常执行的过程当中被 yield 中断了数次,每次中断都会经过 yield 返回当前的迭代值。

也能够手动调用 fab(5) 的 next() 方法(由于 fab(5) 是一个 generator 对象,该对象具备 next() 方法),这样咱们就能够更清楚地看到 fab 的执行流程:


>>> f = fab(3>>> f.1
>>> f.1
>>> f.2
>>> f., line 1,  <module>


 

须要明确的就是生成器也是iterator迭代器,由于它遵循了迭代器协议.

两种建立方式

包含yield的函数

生成器函数跟普通函数只有一点不同,就是把 return 换成yield,其中yield是一个语法糖,内部实现了迭代器协议,同时保持状态能够挂起。以下:

return:

在一个生成器中,若是没有return,则默认执行到函数完毕;若是遇到return,若是在执行过程当中 return,则直接抛出 StopIteration 终止迭代.


def f():

   
yield 5
   print("ooo")
   
return
   yield 6
   print("ppp")
       
# if str(tem)=='None':
       #     print("ok")

f
=f()
# print(f.__next__())
#
print(f.__next__())
for i in f:
   
print(i)

'''
return即迭代结束
for不报错的缘由是内部处理了迭代结束的这种状况
'''


注意:

文件读取


= 1024=


若是直接对文件对象调用 read() 方法,会致使不可预测的内存占用。好的方法是利用固定长度的缓冲区来不断读取文件内容。经过 yield,咱们再也不须要编写读文件的迭代类,就能够轻松实现文件读取。

 

My:生成器对象就是一种特殊的迭代器对象,知足迭代器协议,能够调用next;对生成器对象for 循环时,调用iter方法返回了生成器对象,而后再不断next迭代,而iter和next都是在yield内部实现的。

练习1:使用文件读取,找出文件中最长的行的?

max(len(x.strip()) for x inopen('/hello/abc','r'))

练习2:


 s + i  range(4= n  [1, 10= (add(i, n)  i  list(base)


'''
核心语句就是:
for n in [1, 10]:
base = (add(i, n) for i in base)
在执行list(base)的时候,开始检索,而后生成器开始运算了。关键是,这个循环次数是2,也就是说,有两次生成器表达
式的过程。必须紧紧把握住这一点。
生成器返回去开始运算,n = 10而不是1没问题吧,这个在上面提到的文章中已经提到了,就是add(i, n)绑定的是n这个
变量,而不是它当时的数值。
而后首先是第一次生成器表达式的执行过程:base = (10 + 0, 10 + 1, 10 + 2, 10 +3),这是第一次循环的结
果(形象表示,其实已经计算出来了(10,11,12,3)),而后第二次,
base = (10 + 10, 11 + 10, 12 + 10, 13 + 10) ,终于获得结果了[20, 21, 22, 23].
'''


练习3:自定义range

图片

七 生成器的扩展

生成器对象支持几个方法,如gen.next() ,gen.send() ,gen.throw()等。

 因为没有额外的yield,因此将直接抛出StopIteration。

send的工做方式:


(= 7
     8=

协程应用:

      所谓协同程序也就是是能够挂起,恢复,有多个进入点。其实说白了,也就是说多个函数能够同时进行,能够相互之间发送消息等。


 x  range(4 (+

 x  range(4 (+

 =  i =
                =
相关文章
相关标签/搜索