1. 迭代器

迭代器是访问集合元素的一种方式。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退,不过这也没什么,因为人们很少在迭代途中往后退。另外,迭代器的一大优点是不要求事先准备好整个迭代过程中所有的元素。迭代器仅仅在迭代到某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁。这个特点使得它特别适合用于遍历一些巨大的或是无限的集合,比如几个G的文件。

特点:

a)访问者不需要关心迭代器内部的结构,仅需通过next()方法或不断去取下一个内容

b)不能随机访问集合中的某个值 ,只能从头到尾依次访问

c)访问到一半时不能往回退

d)便于循环比较大的数据集合,节省内存

e)也不能复制一个迭代器。如果要再次(或者同时)迭代同一个对象,只能去创建另一个迭代器对象。enumerate()的返回值就是一个迭代器,我们以enumerate为例:

  1. a = enumerate(['a','b'])
  2.  
  3. for i in range(2): #迭代两次enumerate对象
  4. for x, y in a:
  5. print(x,y)
  6. print(''.center(50,'-'))

结果:

  1. 0 a
  2. 1 b
  3. --------------------------------------------------
  4. --------------------------------------------------

可以看到再次迭代enumerate对象时,没有返回值;

我们可以用linux的文件处理命令vim和cat来理解一下:

a) 读取很大的文件时,vim需要很久,cat是毫秒级;因为vim是一次性把文件全部加载到内存中读取;而cat是加载一行显示一行

b) vim读写文件时可以前进,后退,可以跳转到任意一行;而cat只能向下翻页,不能倒退,不能直接跳转到文件的某一页(因为读取的时候这个“某一页“可能还没有加载到内存中)

正式进入python迭代器之前,我们先要区分两个容易混淆的概念:可迭代对象和迭代器;

可以直接作用于for循环的对象统称为可迭代对象(Iterable)。

可以被next()函数调用并不断返回下一个值的对象称为迭代器(Iterator)。

所有的Iterable均可以通过内置函数iter()来转变为Iterator。

1)可迭代对象

首先,迭代器是一个对象,不是一个函数;是一个什么样的对象呢?就是只要它定义了可以返回一个迭代器的__iter__方法,或者定义了可以支持下标索引的__getitem__方法,那么它就是一个可迭代对象。

python中大部分对象都是可迭代的,比如list,tuple等。如果给一个准确的定义的话,看一下list,tuple类的源码,都有__iter__(self)方法。

常见的可迭代对象:

a) 集合数据类型,如listtupledictsetstr等;

b) generator,包括生成器和带yield的generator function。

注意:生成器都是Iterator对象,但listdictstr虽然是Iterable,却不是Iterator,关于生成器,继续往下看

如何判断一个对象是可迭代对象呢?可以通过collections模块的Iterable类型判断:

  1. >>> from collections import Iterable
  2. >>> isinstance([], Iterable)
  3. True
  4. >>> isinstance({}, Iterable)
  5. True
  6. >>> isinstance('abc', Iterable)
  7. True
  8. >>> isinstance((x for x in range(10)), Iterable)
  9. True
  10. >>> isinstance(100, Iterable)
  11. False

2)迭代器

一个可迭代对象是不能独立进行迭代的,Python中,迭代是通过for ... in来完成的。

for循环在迭代一个可迭代对象的过程中都做了什么呢?

a)当for循环迭代一个可迭代对象时,首先会调用可迭代对象的__iter__()方法,然我们看看源码中关于list类的__iter__()方法的定义:

  1. def __iter__(self, *args, **kwargs): # real signature unknown
  2. """ Implement iter(self). """
  3. pass

__iter__()方法调用了iter(self)函数,我们再来看一下iter()函数的定义:

  1. def iter(source, sentinel=None): # known special case of iter
  2. """
  3. iter(iterable) -> iterator
  4. iter(callable, sentinel) -> iterator
  5.  
  6. Get an iterator from an object. In the first form, the argument must
  7. supply its own iterator, or be a sequence.
  8. In the second form, the callable is called until it returns the sentinel.
  9. """
  10. pass

iter()函数的参数是一个可迭代对象,最终返回一个迭代器

b) for循环会不断调用迭代器对象的__next__()方法(python2.x中是next()方法),每次循环,都返回迭代器对象的下一个值,直到遇到StopIteration异常。

  1. >>> lst_iter = iter([1,2,3])
  2. >>> lst_iter.__next__()
  3. 1
  4. >>> lst_iter.__next__()
  5. 2
  6. >>> lst_iter.__next__()
  7. 3
  8. >>> lst_iter.__next__()
  9. Traceback (most recent call last):
  10. File "<stdin>", line 1, in <module>
  11. StopIteration
  12. >>>

这里注意:这里的__next__()方法和内置函数next(iterator, default=None)不是一个东西;(内置函数next(iterator, default=None)也可以返回迭代器的下一个值)

c) 而for循环可以捕获StopIteration异常并结束循环;

总结一下:

a)for....in iterable,会通过调用iter(iterable)函数(实际上,首先调用的对象的__iter__()方法),返回一个迭代器iterator;

b)每次循环,调用一次对象的__next__(self),直到最后一个值,再次调用会触发StopIteration

c)for循环捕捉到StopIteration,从而结束循环

上面说了这么多,到底什么是迭代器Iterator呢?

任何实现了__iter____next__()(python2中实现next())方法的对象都是迭代器,__iter__返回迭代器自身,__next__返回容器中的下一个值;

既然知道了什么迭代器,那我们自定义一个迭代器玩玩:

  1. class Iterator_test(object):
  2. def __init__(self, data):
  3. self.data = data
  4. self.index = len(data)
  5.  
  6. def __iter__(self):
  7. return self
  8.  
  9. def __next__(self):
  10. if self.index <= 0 :
  11. raise StopIteration
  12. self.index -= 1
  13. return self.data[self.index]
  14.  
  15. iterator_winter = Iterator_test('abcde')
  16.  
  17. for item in iterator_winter:
  18. print(item)

如何判断一个对象是一个迭代器对象呢?两个方法:

1)通过内置函数next(iterator, default=None),可以看到next的第一个参数必须是迭代器;所以迭代器也可以认为是可以被next()函数调用的对象

2)通过collection中的Iterator类型判断

  1. >>> from collections import Iterator
  2. >>>
  3. >>> isinstance([1,2,3], Iterator)
  4. False
  5. >>> isinstance(iter([1,2,3]), Iterator)
  6. True
  7. >>> isinstance([1,2,3].__iter__(), Iterator)
  8. True
  9. >>>

这里大家会不会有个疑问:

对于迭代器而言,看上去作用的不就是__next__方法嘛,__iter__好像没什么卵用,干嘛还需要__iter__方法呢?

我们知道,python中迭代是通过for循环实现的,而for循环的循环对象必须是一个可迭代对象Iterable,而Iterable必须是一个实现了__iter__方法的对象;知道为什么需要__iter__魔术方法了吧;

那么我就是想自定义一个没有实现__iter__方法的迭代器可以吗?可以,像下面这样:

  1. class Iterable_test(object):
  2. def __init__(self, data):
  3. self.data = data
  4.  
  5. def __iter__(self):
  6. return Iterator_test(self.data)
  7.  
  8. class Iterator_test(object):
  9. def __init__(self, data):
  10. self.data = data
  11. self.index = len(data)
  12.  
  13. def __next__(self):
  14. if self.index <= 0 :
  15. raise StopIteration
  16. self.index -= 1
  17. return self.data[self.index]
  18.  
  19. iterator_winter = Iterable_test('abcde')
  20.  
  21. for item in iterator_winter:
  22. print(item)

先定义一个可迭代对象(包含__iter__方法),然后该对象返回一个迭代器;这样看上去是不是很麻烦?是不是同时带有__iter__和__next__魔术方法的迭代器更好呢!

同时,这里要纠正之前的一个迭代器概念:只要__next__()(python2中实现next())方法的对象都是迭代器;

既然这样,只需要迭代器Iterator接口就够了,为什么还要设计可迭代对象Iterable呢?

这个和迭代器不能重复使用有关,下面同意讲解:

3)总结和一些重要知识点

a) 如何复制迭代器

之前在使用enumerate时,我们说过enumerate对象通过for循环迭代一次后就不能再被迭代:

  1. >>> e = enumerate([1,2,3])
  2. >>>
  3. >>> for x,y in e:
  4. ... print(x,y)
  5. ...
  6. 0 1
  7. 1 2
  8. 2 3
  9. >>> for x,y in e:
  10. ... print(x,y)
  11. ...
  12. >>>

这是因为enumerate是一个迭代器;

迭代器是一次性消耗品,当循环以后就空了。不能再次使用;通过深拷贝可以解决;

  1. >>> import copy
  2. >>>
  3. >>> e = enumerate([1,2,3])
  4. >>>
  5. >>> e_deepcopy = copy.deepcopy(e)
  6. >>>
  7. >>> for x,y in e:
  8. ... print(x,y)
  9. ...
  10. 0 1
  11. 1 2
  12. 2 3
  13. >>> for x,y in e_deepcopy:
  14. ... print(x,y)
  15. ...
  16. 0 1
  17. 1 2
  18. 2 3
  19. >>>

b)为什么不只保留Iterator的接口而还需要设计Iterable呢?

因为迭代器迭代一次以后就空了,那么如果list,dict也是一个迭代器,迭代一次就不能再继续被迭代了,这显然是反人类的;所以通过__iter__每次返回一个独立的迭代器,就可以保证不同的迭代过程不会互相影响。而生成器表达式之类的结果往往是一次性的,不可以重复遍历,所以直接返回一个Iterator就好。让Iterator也实现Iterable的兼容就可以很灵活地选择返回哪一种。

总结说,Iterator实现的__iter__是为了兼容Iterable的接口,从而让Iterator成为Iterable的一种实现。

另外,迭代器是惰性的,只有在需要返回下一个数据时它才会计算。就像一个懒加载的工厂,等到有人需要的时候才给它生成值返回,没调用的时候就处于休眠状态等待下一次调用。所以,Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。

c)通过__getitem__来实现for循环

前面关于可迭代对象的定义是这样的:定义了可以返回一个迭代器的__iter__方法,或者定义了可以支持下标索引的__getitem__方法,那么它就是一个可迭代对象。

但是如果对象没有__iter__,但是实现了__getitem__,会改用下标迭代的方式。

  1. class NoIterable(object):
  2. def __init__(self, data):
  3. self.data = data
  4. def __getitem__(self, item):
  5. return self.data[item]
  6.  
  7. no_iter = NoIterable('abcde')
  8. for item in no_iter:
  9. print(item)
当for发现没有__iter__但是有__getitem__的时候,会从0开始依次读取相应的下标,直到发生IndexError为止,这是一种旧的迭代方法。iter方法也会处理这种情况,在不存在__iter__的时候,返回一个下标迭代的iterator对象来代替。
d)一张图总结迭代器
e)使用迭代器来实现一个斐波那契数列

  1. class Fib(object):
  2. def __init__(self, limit):
  3. self.a, self.b = 0, 1
  4. self.limit = limit
  5.  
  6. def __iter__(self):
  7. return self
  8.  
  9. def __next__(self):
  10. self.a, self.b = self.b, self.a+self.b
  11. while self.a > self.limit:
  12. raise StopIteration
  13. return self.a
  14.  
  15. for n in Fib(1000):
  16. print(n)

2. 生成器

理解了迭代器以后,生成器就会简单很多,因为生成器其实是一种特殊的迭代器。不过这种迭代器更加优雅。它不需要再像上面的类一样写__iter__()__next__()方法了,只需要一个yiled关键字。 生成器一定是迭代器(反之不成立),因此任何生成器也是以一种懒加载的模式生成值。

语法上说,生成器函数是一个带yield关键字的函数。

调用生成器函数后会得到一个生成器对象,这个生成器对象实际上就是一个特殊的迭代器,拥有__iter__()__next__()方法

我们先用一个例子说明一下:

  1. >>> def generator_winter():
  2. ... i = 1
  3. ... while i <= 3:
  4. ... yield i
  5. ... i += 1
  6. ...
  7. >>> generator_winter
  8. <function generator_winter at 0x000000000323B9D8>
  9. >>> generator_iter = generator_winter()
  10. >>> generator_iter
  11. <generator object generator_winter at 0x0000000002D9CAF0>
  12. >>>
  13. >>> generator_iter.__next__()
  14. 1
  15. >>> generator_iter.__next__()
  16. 2
  17. >>> generator_iter.__next__()
  18. 3
  19. >>> generator_iter.__next__()
  20. Traceback (most recent call last):
  21. File "<stdin>", line 1, in <module>
  22. StopIteration
  23. >>>

现在解释一下上面的代码:

a)首先我们创建了一个含有yield关键字的函数generator_winter,这是一个生成器函数

b)然后,我们调用了这个生成器函数,并且将返回值赋值给了generator_iter,generator_iter是一个生成器对象;注意generator_iter = generator_winter()时,函数体中的代码并不会执行,只有显示或隐示地调用next的时候才会真正执行里面的代码。

c)生成器对象就是一个迭代器,所以我们可以调用对象的__next__方法来每次返回一个迭代器的值;迭代器的值通过yield返回;并且迭代完最后一个元素后,触发StopIteration异常;

既然生成器对象是一个迭代器,我们就可以使用for循环来迭代这个生成器对象:

  1. >>> def generator_winter():
  2. ... i = 1
  3. ... while i <= 3:
  4. ... yield i
  5. ... i += 1
  6. ...
  7. >>>
  8. >>> for item in generator_winter():
  9. ... print(item)
  10. ...
  11. 1
  12. 2
  13. 3
  14. >>>

我们注意到迭代器不是使用return来返回值,而是采用yield返回值;那么这个yield有什么特别之处呢?

1)yield

我们知道,一个函数只能返回一次,即return以后,这次函数调用就结束了;

但是生成器函数可以暂停执行,并且通过yield返回一个中间值,当生成器对象的__next__()方法再次被调用的时候,生成器函数可以从上一次暂停的地方继续执行,直到触发一个StopIteration

上例中,当执行到yield i后,函数返回i值,然后print这个值,下一次循环,又调用__next__()方法,回到生成器函数,并从yield i的下一句继续执行;

摘一段<python核心编程>的内容:

生成器的另外一个方面甚至更加强力----协同程序的概念。协同程序是可以运行的独立函数调用,可以暂停或者挂起,并从程序离开的地方继续或者重新开始。在有调用者和(被调用的)协同程序也有通信。举例来说,当协同程序暂停时,我们仍可以从其中获得一个中间的返回值,当调用回到程序中时,能够传入额外或者改变了的参数,但是仍然能够从我们上次离开的地方继续,并且所有状态完整。挂起返回出中间值并多次继续的协同程序被称为生成器,那就是python的生成真正在做的事情。这些提升让生成器更加接近一个完全的协同程序,因为允许值(和异常)能传回到一个继续的函数中,同样的,当等待一个生成器的时候,生成器现在能返回控制,在调用的生成器能挂起(返回一个结果)之前,调用生成器返回一个结果而不是阻塞的等待那个结果返回。

2) 什么情况会触发StopIteration

两种情况会触发StopIteration

a) 如果没有return,则默认执行到函数完毕时返回StopIteration;

b) 如果在执行过程中 return,则直接抛出 StopIteration 终止迭代;

c) 如果在return后返回一个值,那么这个值为StopIteration异常的说明,不是程序的返回值。

  1. >>> def generator_winter():
  2. ... yield 'hello world'
  3. ... return 'again'
  4. ...
  5. >>>
  6. >>> winter = generator_winter()
  7. >>> winter.__next__()
  8. 'hello world'
  9. >>> winter.__next__()
  10. Traceback (most recent call last):
  11. File "<stdin>", line 1, in <module>
  12. StopIteration: again
  13. >>>

3) 生成器的作用

说了这么多,生成器有什么用呢?作为python主要特性之一,这是个极其牛逼的东西,由于它是惰性的,在处理大型数据时,可以节省大量内存空间;

当你需要迭代一个巨大的数据集合,比如创建一个有规律的100万个数字,如果采用列表来存储访问,那么会占用大量的内存空间;而且如果我们只是访问这个列表的前几个元素,那么后边大部分元素占据的内存空间就白白浪费了;这时,如果采用生成器,则不必创建完整的列表,一次循环返回一个希望得到的值,这样就可以大量节省内存空间;

这里在举例之前,我们先介绍一个生成器表达式(类似于列表推导式,只是把[]换成()),这样就创建了一个生成器。

  1. >>> gen = (x for x in range(10))
  2. >>> gen
  3. <generator object <genexpr> at 0x0000000002A923B8>
  4. >>>

生成器表达式的语法如下:

  1. (expr for iter_var in iterable if cond_expr)

用生成器来实现斐波那契数列

  1. def fib(n):
  2. a, b = 0, 1
  3. while b <= n:
  4. yield b
  5. a, b = b, a+b
  6.  
  7. f = fib(10)
  8. for item in f:
  9. print(item)

 4)生成器方法

直接看生成器源代码

  1. class __generator(object):
  2. '''A mock class representing the generator function type.'''
  3. def __init__(self):
  4. self.gi_code = None
  5. self.gi_frame = None
  6. self.gi_running = 0
  7.  
  8. def __iter__(self):
  9. '''Defined to support iteration over container.'''
  10. pass
  11.  
  12. def __next__(self):
  13. '''Return the next item from the container.'''
  14. pass
  15.  
  16. def close(self):
  17. '''Raises new GeneratorExit exception inside the generator to terminate the iteration.'''
  18. pass
  19.  
  20. def send(self, value):
  21. '''Resumes the generator and "sends" a value that becomes the result of the current yield-expression.'''
  22. pass
  23.  
  24. def throw(self, type, value=None, traceback=None):
  25. '''Used to raise an exception inside the generator.'''
  26. pass

首先看到了生成器是自带__iter__和__next__魔术方法的;

a)send

生成器函数最大的特点是可以接受外部传入的一个变量,并根据变量内容计算结果后返回。这是生成器函数最难理解的地方,也是最重要的地方,协程的实现就全靠它了

看一个小猫吃鱼的例子:

  1. def cat():
  2. print('我是一只hello kitty')
  3. while True:
  4. food = yield
  5. if food == '鱼肉':
  6. yield '好开心'
  7. else:
  8. yield '不开心,人家要吃鱼肉啦'

中间有个赋值语句food = yield,可以通过send方法来传参数给food,试一下:

情况1)

  1. miao = cat() #只是用于返回一个生成器对象,cat函数不会执行
  2. print(''.center(50,'-'))
  3. print(miao.send('鱼肉'))

结果:

  1. Traceback (most recent call last):
  2. --------------------------------------------------
  3. File "C:/Users//Desktop/Python/cnblogs/subModule.py", line 67, in <module>
  4. print(miao.send('鱼肉'))
  5. TypeError: can't send non-None value to a just-started generator

看到了两个信息:

a)miao = cat() ,只是用于返回一个生成器对象,cat函数不会执行

b)can't send non-None value to a just-started generator;不能给一个刚创建的生成器对象直接send值

改一下

情况2)

  1. miao = cat()
  2. miao.__next__()
  3. print(miao.send('鱼肉'))

结果:

  1. 我是一只hello kitty
  2. 好开心

没毛病,那么到底send()做了什么呢?send()的帮助文档写的很清楚,'''Resumes the generator and "sends" a value that becomes the result of the current yield-expression.''';可以看到send依次做了两件事:

a)回到生成器挂起的位置,继续执行

b)并将send(arg)中的参数赋值给对应的变量,如果没有变量接收值,那么就只是回到生成器挂起的位置

但是,我认为send还做了第三件事:

c)兼顾__next__()作用,挂起程序并返回值,所以我们在print(miao.send('鱼肉'))时,才会看到'好开心';其实__next__()等价于send(None)

所以当我们尝试这样做的时候:

  1. def cat():
  2. print('我是一只hello kitty')
  3. while True:
  4. food = yield
  5. if food == '鱼肉':
  6. yield '好开心'
  7. else:
  8. yield '不开心,人家要吃鱼肉啦'
  9.  
  10. miao = cat()
  11. print(miao.__next__())
  12. print(miao.send('鱼肉'))
  13. print(miao.send('骨头'))
  14. print(miao.send('鸡肉'))

就会得到这个结果:

  1. 我是一只hello kitty
  2. None
  3. 好开心
  4. None
  5. 不开心,人家要吃鱼肉啦

我们按步骤分析一下:

a)执行到print(miao.__next__()),执行cat()函数,print了”我是一只hello kitty”,然后在food = yield挂起,并返回了None,打印None

b)接着执行print(miao.send('鱼肉')),回到food = yield,并将'鱼肉’赋值给food,生成器函数恢复执行;直到运行到yield '好开心',程序挂起,返回'好开心',并print'好开心'

c)接着执行print(miao.send('骨头')),回到yield '好开心',这时没有变量接收参数'骨头',生成器函数恢复执行;直到food = yield,程序挂起,返回None,并print None

d)接着执行print(miao.send('鸡肉')),回到food = yield,并将'鸡肉’赋值给food,生成器函数恢复执行;直到运行到yield'不开心,人家要吃鱼肉啦',程序挂起,返回'不开心,人家要吃鱼肉啦',,并print '不开心,人家要吃鱼肉啦'

大功告成;那我们优化一下代码:

  1. def cat():
  2. msg = '我是一只hello kitty'
  3. while True:
  4. food = yield msg
  5. if food == '鱼肉':
  6. msg = '好开心'
  7. else:
  8. msg = '不开心,人家要吃鱼啦'
  9.  
  10. miao = cat()
  11. print(miao.__next__())
  12. print(miao.send('鱼肉'))
  13. print(miao.send('鸡肉'))

我们再看一个更实用的例子,一个计数器

  1. def counter(start_at = 0):
  2. count = start_at
  3. while True:
  4. val = (yield count)
  5. if val is not None:
  6. count = val
  7. else:
  8. count += 1
  9.  
  10. count = counter(5)
  11. print(count.__next__())
  12. print(count.__next__())
  13. print(count.send(0))
  14. print(count.__next__())
  15. print(count.__next__())

结果:

  1. 5
  2. 6
  3. 0
  4. 1
  5. 2

b)close

帮助文档:'''Raises new GeneratorExit exception inside the generator to terminate the iteration.'''

手动关闭生成器函数,后面的调用会直接返回StopIteration异常

  1. >>> def gene():
  2. ... while True:
  3. ... yield 'ok'
  4. ...
  5. >>> g = gene()
  6. >>> g.__next__()
  7. 'ok'
  8. >>> g.__next__()
  9. 'ok'
  10. >>> g.close()
  11. >>> g.__next__()
  12. Traceback (most recent call last):
  13. File "<stdin>", line 1, in <module>
  14. StopIteration
  15. >>>

在close以后再执行__next__会触发StopIteration异常

c)throw

用来向生成器函数送入一个异常,throw()后直接抛出异常并结束程序,或者消耗掉一个yield,或者在没有下一个yield的时候直接进行到程序的结尾。

  1. >>> def gene():
  2. ... while True:
  3. ... try:
  4. ... yield 'normal value'
  5. ... except ValueError:
  6. ... yield 'we got ValueError here'
  7. ... except TypeError:
  8. ... break
  9. ...
  10. >>> g = gene()
  11. >>> print(g.__next__())
  12. normal value
  13. >>> print(g.__next__())
  14. normal value
  15. >>> print(g.throw(ValueError))
  16. we got ValueError here
  17. >>> print(g.__next__())
  18. normal value
  19. >>> print(g.throw(TypeError))
  20. Traceback (most recent call last):
  21. File "<stdin>", line 1, in <module>
  22. StopIteration
  23. >>>

5)通过yield实现单线程情况下的异步并发效果

  1. def consumer(name):
  2. print('%s准备吃包子了' % name)
  3. while True:
  4. baozi_name = yield
  5. print('[%s]来了,被[%s]吃了'% (baozi_name, name))
  6.  
  7. def producer(*name):
  8. c1 = consumer(name[0])
  9. c2 = consumer(name[1])
  10. c1.__next__()
  11. c2.__next__()
  12. for times in range(5):
  13. print('做了两个包子')
  14. c1.send('豆沙包%s'%times)
  15. c2.send('菜包%s'%times)
  16.  
  17. producer('winter', 'elly')

效果:

  1. winter准备吃包子了
  2. elly准备吃包子了
  3. 做了两个包子
  4. [豆沙包0]来了,被[winter]吃了
  5. [菜包0]来了,被[elly]吃了
  6. 做了两个包子
  7. [豆沙包1]来了,被[winter]吃了
  8. [菜包1]来了,被[elly]吃了
  9. 做了两个包子
  10. [豆沙包2]来了,被[winter]吃了
  11. [菜包2]来了,被[elly]吃了
  12. 做了两个包子
  13. [豆沙包3]来了,被[winter]吃了
  14. [菜包3]来了,被[elly]吃了
  15. 做了两个包子
  16. [豆沙包4]来了,被[winter]吃了
  17. [菜包4]来了,被[elly]吃了

创建了两个独立的生成器,很有趣,很吊;

6)补充几个小例子:

a)使用生成器创建一个range

  1. def range(n):
  2. count = 0
  3. while count < n:
  4. yield count
  5. count += 1

b ) 使用生成器监听文件输入

  1. def fileTail(filename):
  2. with open(filename) as f:
  3. while True:
  4. tail = f.readline()
  5. if line:
  6. yield tail
  7. else:
  8. time.sleep(0.1)

c)计算移动平均值

  1. def averager(start_with = 0):
  2. count = 0
  3. aver = start_with
  4. total = start_with
  5. while True:
  6. val = yield aver
  7. total += val
  8. count += 1
  9. aver = total/count

有个弊端,需要通过__next__或next()初始化一次,通过预激解决

d)预激计算移动平均值

  1. def init(f):
  2. def wrapper(start_with = 0):
  3. g_aver = f(start_with)
  4. g_aver.__next__()
  5. return g_aver
  6. return wrapper
  7.  
  8. @init
  9. def averager(start_with = 0):
  10. count = 0
  11. aver = start_with
  12. total = start_with
  13. while True:
  14. val = yield aver
  15. total += val
  16. count += 1
  17. aver = total/count

e)读取文件字符数最多的行的字符数

最传统的写法:

  1. def longestLine(filename):
  2. with open(filename, 'r', encoding='utf-8') as f:
  3. alllines = [len(x.strip()) for x in f]
  4. return max(alllines)

使用生成器以后的写法:

  1. def longestLine(filename):
  2. return max(len(x.strip()) for x in open(filename))

f)多生成器迭代

  1. >>> g = (i for i in range(5))
  2. >>> for j in g:
  3. ... print(j)
  4. ...
  5. 0
  6. 1
  7. 2
  8. 3
  9. 4
  10. >>> for j in g:
  11. ... print(j)
  12. ...
  13. >>>

因为for j in g, 每次循环执行一次g.__next__();直到结束,触发StopIteration;

主意下面结果的输出:

  1. >>> g = (i for i in range(4))
  2. >>> g1 = (x for x in g)
  3. >>> g2 = (y for y in g1)
  4. >>>
  5. >>> print(list(g1))
  6. [0, 1, 2, 3]
  7. >>> print(list(g2))
  8. []
  9. >>>

为什么print(list(g2))为空呢?理一下,不然会乱:

看下面的代码:

  1. def g():
  2. print('1.1')
  3. for i in range(2):
  4. print('1.2')
  5. yield i
  6. print('1.3')
  7.  
  8. def g1():
  9. print('2.1')
  10. for x in s:
  11. print('2.2')
  12. yield x
  13. print('2.3')
  14.  
  15. def g2():
  16. print('3.1')
  17. for y in s1:
  18. print('3.2')
  19. yield y
  20. print('3.3')
  21.  
  22. s = g()
  23. s1 = g1()
  24. s2 = g2()
  25. print('start first list')
  26. print(list(s1))
  27. print('start second list')
  28. print(list(s2))

结果:

  1. start first list
  2. 2.1
  3. 1.1
  4. 1.2
  5. 2.2
  6. 2.3
  7. 1.3
  8. 1.2
  9. 2.2
  10. 2.3
  11. 1.3
  12. [0, 1]
  13. start second list
  14. 3.1
  15. []

注意第11行之后,g触发了StopIteration,被for x in s捕捉,即不能继续s.__next__()了;同样的g1触发StopIteration,被list捕捉,即不能继续s1.__next__()了;于是打印[0,1]

当进行print(list(s2))时,执行s2.__next__(),停留在代码的第17行for y in s1,但是这是不能继续s1.__next__()了;于是直接触发了StopIteration;结果为[]

再看一个有意思的输出:

  1. def add(n,i):
  2. return n+i
  3.  
  4. g = (i for i in range(4))
  5.  
  6. for n in [1,10]:
  7. g = (add(n,i) for i in g)
  8.  
  9. print(list(g))

输出为:

  1. [20, 21, 22, 23]

其实上面的代码翻译如下:

  1. def add(n,i):
  2. return n+i
  3.  
  4. def g1():
  5. for i in g:
  6. yield add(n,i)
  7.  
  8. def g2():
  9. for i in s1:
  10. yield add(n,i)
  11.  
  12. n = 1
  13. s1 = g1()
  14. n = 10
  15. s2 = g2()
  16. print(list(s2))

最终n用的是10,

  1.  

Python迭代器,生成器--精华中的精华的更多相关文章

  1. Python迭代器生成器与生成式

    Python迭代器生成器与生成式 什么是迭代 迭代是重复反馈过程的活动,其目的通常是为了逼近所需目标或结果.每一次对过程的重复称为一次"迭代",而每一次迭代得到的结果会作为下一次迭 ...

  2. Python 迭代器&生成器

    1.内置参数     Built-in Functions     abs() dict() help() min() setattr() all() dir() hex() next() slice ...

  3. python 迭代器 生成器

    迭代器 生成器 一 什么是迭代器协议 1.迭代器协议是指:对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么就引起一个StopIteration异常,以终止迭代 (只能往后走不能往前 ...

  4. Python 迭代器&生成器,装饰器,递归,算法基础:二分查找、二维数组转换,正则表达式,作业:计算器开发

    本节大纲 迭代器&生成器 装饰器  基本装饰器 多参数装饰器 递归 算法基础:二分查找.二维数组转换 正则表达式 常用模块学习 作业:计算器开发 实现加减乘除及拓号优先级解析 用户输入 1 - ...

  5. python迭代器,生成器,推导式

    可迭代对象 字面意思分析:可以重复的迭代的实实在在的东西. list,dict(keys(),values(),items()),tuple,str,set,range, 文件句柄(待定) 专业角度: ...

  6. 4.python迭代器生成器装饰器

    容器(container) 容器是一种把多个元素组织在一起的数据结构,容器中的元素可以逐个地迭代获取,可以用in, not in关键字判断元素是否包含在容器中.通常这类数据结构把所有的元素存储在内存中 ...

  7. python迭代器生成器

    1.生成器和迭代器.含有yield的特殊函数为生成器.可以被for循环的称之为可以迭代的.而可以通过_next()_调用,并且可以不断返回值的称之为迭代器 2.yield简单的生成器 #迭代器简单的使 ...

  8. Python迭代器生成器,私有变量及列表字典集合推导式(二)

    1 python自省机制 这个是python一大特性,自省就是面向对象的语言所写的程序在运行时,能知道对象的类型,换句话说就是在运行时能获取对象的类型,比如通过 type(),dir(),getatt ...

  9. Python迭代器生成器,模块和包

      1.迭代器和生成器 2.模块和包 1.迭代器 迭代器对象要求支持迭代器协议的对象,在Python中,支持迭代器协议就是实现对象的__iter__()和__next__()方法.    其中__it ...

随机推荐

  1. CY7C68013A控制传输

    大家好,你们的大熊又回来了.本篇文章我们来重点了解一下USB设备的四大传输方式之一--控制传输.不同于其他三种传输方式,控制传输有其独特的作用和功能,是一个USB设备必须支持的传输方式.控制传输对带宽 ...

  2. 7.7 WPF后台代码绑定如果是属性,必须指定一下数据上下文才能实现,而函数(click)就不用

    如: private bool _IsExportWithImage; /// <summary> /// 是否选择导出曲线图 /// </summary> public bo ...

  3. POI实现excel各种验证和导入的思路总结

      制定标准 导入总是与导出相辅相成的,无规矩不成方圆.所谓的标准都是大家一同来维护和遵守的,那么首先就是制定一个模板. 这样可以减少验证的工作量. 例如时间的规范[yyyy-MM-dd],获取单元格 ...

  4. C# 三层架构之系统的登录验证与添加数据的实现

    利用三层架构体系,实现学生管理系统中用户的登录与添加班级信息的功能,一下代码为具体实现步骤的拆分过程: 一.用户登录界面功能的实现 1.在数据访问层(LoginDAL)进行对数据库中数据的访问操作 u ...

  5. Porita详解----Items

    Items(项目) 一个item是指从目标网站上爬取的一条单独的数据.例如从京东网站上爬取的一款小米6手机的信息.大家应该对 item (项目)和 item definition(项目定义)做一个区分 ...

  6. echarts堆叠图展示,根据数据维度的粒度判断是否展示数据

    1.定义一个参数,返回根据判断什么条件是否显示值; 2.var a = '<%=(String)request.getAttribute("type")%&>' ...

  7. JAVA HashMap的实现原理

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt359 1. HashMap的数据结构 数据结构中有数组和链表来实现对数据的存 ...

  8. Linq 实现sql中的not in和in条件查询

    T-SQL的IN: Select ProductID, ProductName, CategoryID From dbo.Products Where CategoryID in (1, 2) T-S ...

  9. Kafka Streams 剖析

    1.概述 Kafka Streams 是一个用来处理流式数据的库,属于Java类库,它并不是一个流处理框架,和Storm,Spark Streaming这类流处理框架是明显不一样的.那这样一个库是做什 ...

  10. 发布一个Python小程序:ManHourCalendar

    程序诞生的那些事儿 先聊聊背景资料档案.. 大约两年前,我只身前往岛国赚点外快.在那边的派遣制度工作中,存在一个大约叫每月的标准工作时间的概念,按照自家公司跟派遣目标公司(业界称为现场)的合同,规定了 ...