python学习记录——迭代器和生成器

Python的数据结构:容器(container)、可迭代对象(iterable)、迭代器(iterator)、生成器(generator)、列表/集合/字典推导式(list,set,dict comprehension)的关系如下图。

relations

容器(container)

容器是一种把多个元素组织在一起的数据结构,容器中的元素可以逐个地迭代获取,可以用innot in关键字判断元素是否包含在容器中。通常这类数据结构把所有的元素存储在内存中(也有一些特例,并不是所有的元素都放在内存,比如迭代器和生成器对象)在Python中,常见的容器对象有:

  • list, deque, ….
  • set, frozensets, ….
  • dict, defaultdict, OrderedDict, Counter, ….
  • tuple, namedtuple, …
  • str

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

>>> assert 1 in [1, 2, 3]      # lists

>>> assert 4 not in [1, 2, 3]

>>> assert 1 in {1, 2, 3}      # sets

>>> assert 4 not in {1, 2, 3}

>>> assert 1 in (1, 2, 3)      # tuples

>>> assert 4 not in (1, 2, 3)

询问某元素是否在dict中用dict的中key:

>>> d = {1: 'foo', 2: 'bar', 3: 'qux'}

>>> assert 1 in d

>>> assert 'foo' not in d  # 'foo' 不是dict中的元素

询问某substring是否在string中:

>>> s = 'foobar'

>>> assert 'b' in s

>>> assert 'x' not in s

>>> assert 'foo' in s


可迭代对象(iterable)

尽管绝大多数容器都提供了某种方式来获取其中的每一个元素,但这并不是容器本身提供的能力,而是可迭代对象赋予了容器这种能力,当然并不是所有的容器都是可迭代的,比如:Bloom filter,虽然Bloom filter可以用来检测某个元素是否包含在容器中,但是并不能从容器中获取其中的每一个值,因为Bloom filter压根就没把元素存储在容器中,而是通过一个散列函数映射成一个值保存在数组中。

刚才说过,很多容器都是可迭代对象,此外还有更多的对象同样也是可迭代对象,比如处于打开状态的files,sockets等等。但凡是可以返回一个迭代器的对象都可称之为可迭代对象,听起来可能有点困惑,没关系,先看一个例子:

>>> a = [1, 2, 3]
>>> b = iter(a)
>>> type(a)
<class 'list'>
>>> type(b)
<class 'list_iterator'>
>>> next(b)
1
>>> next(b)
2
>>> next(b)
3
>>> next(b)
Traceback (most recent call last):
  File "<pyshell#16>", line 1, in <module>
    next(b)
StopIteration
>>> 


当运行代码:这里a是一个可迭代对象,可迭代对象和容器一样是一种通俗的叫法,并不是指某种具体的数据类型,list是可迭代对象,dict是可迭代对象,set也是可迭代对象。b是迭代器,迭代器内部持有一个状态,该状态用于记录当前迭代所在的位置,以方便下次迭代的时候获取正确的元素。迭代器有一种具体的迭代器类型,比如list_iteratorset_iterator。可迭代对象实现了__iter__方法,该方法返回一个迭代器对象。

x = [1, 2, 3]

for elem in x:

    ...

反编译该段代码,你可以看到解释器显示地调用GET_ITER指令,相当于调用iter(x)FOR_ITER指令就是调用next()方法,不断地获取迭代器中的下一个元素,但是你没法直接从指令中看出来,因为他被解释器优化过了。实际执行情况是:
iterable-vs-iterator.png

>>> import dis

>>> x = [1, 2, 3]

>>> dis.dis('for _ in x: pass')

  1           0 SETUP_LOOP              14 (to 17)

              3 LOAD_NAME                0 (x)

              6 GET_ITER

        >>    7 FOR_ITER                 6 (to 16)

             10 STORE_NAME               1 (_)

             13 JUMP_ABSOLUTE            7

        >>   16 POP_BLOCK

        >>   17 LOAD_CONST               0 (None)

             20 RETURN_VALUE

迭代器(iterator)

什么是迭代器?

指的是一个重复的过程,每一次重复称为一次迭代,并且每一次重复的结果是下一次重复的初始值

为什么要有迭代器

  1. 对于序列类型:str,list,tuple,可以依赖索引来迭代取值 
  2. 对于dict,set,文件,python必须为我们提供一种不依赖于索引的迭代取值的方式—>迭代器

那么什么迭代器呢?它是一个带状态的对象,他能在你调用next()方法的时候返回容器中的下一个值,任何实现了__iter____next__()(python2中实现next())方法的对象都是迭代器,__iter__返回迭代器自身,__next__返回容器中的下一个值,如果容器中没有更多元素了,则抛出StopIteration异常,至于它们到底是如何实现的这并不重要。

所以,迭代器就是实现了工厂模式的对象,它在你每次你询问要下一个值的时候给你返回。有很多关于迭代器的例子,比如itertools函数返回的都是迭代器对象。

生成无限序列:

import itertools

colors = itertools.cycle(['blue', 'red', 'yellow'])
for x in colors:
    print(x)

结果会一直无限循环打印:blue、red、yellow

>>> from itertools import cycle

>>> colors = cycle(['red', 'white', 'blue'])

>>> next(colors)

'red'

>>> next(colors)

'white'

>>> next(colors)

'blue'

>>> next(colors)

'red'


从无限的序列中生成有限序列:从一个有限序列中生成无限序列:

>>> from itertools import islice

>>> colors = cycle(['red', 'white', 'blue'])  # infinite

>>> limited = islice(colors, 0, 4)            # finite

>>> for x in limited:                        

...     print(x)

red

white

blue

red

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

生成器(generator)

生成器算得上是Python语言中最吸引人的特性之一,生成器其实是一种特殊的迭代器,不过这种迭代器更加优雅。只要在函数体内出现yield关键字,那么再执行函数就不会执行函数代码,会得到一个结果,该结果就是生成器。它不需要再像上面的类一样写__iter__()__next__()方法了,只需要一个yiled关键字。 生成器一定是迭代器(反之不成立),因此任何生成器也是以一种懒加载的模式生成值。用生成器来实现斐波那契数列的例子是:

>>> def func():
    print('---->1')
    yield 1
    print('---->2')
    yield 2
    print('---->3')
    yield 3

    
>>> a = func()
>>> a
<generator object func at 0x00000000031FA780>
>>> next(a)
---->1
1
>>> next(a)
---->2
2
>>> next(a)
---->3
3
>>> next(a)
Traceback (most recent call last):
  File "<pyshell#8>", line 1, in <module>
    next(a)
StopIteration

yield与return的区别:

  1. yield可以返回多次值 
  2. 函数暂停与再继续的状态是由yield帮我们保存的 
  3. yield在函数中也就是暂停的意思,并且返回yield后面的值

总结

  • 容器是一系列元素的集合,str、list、set、dict、file、sockets对象都可以看作是容器,容器都可以被迭代(用在for,while等语句中),因此他们被称为可迭代对象。
  • 可迭代对象实现了__iter__方法,该方法返回一个迭代器对象。
  • 迭代器持有一个内部状态的字段,用于记录下次迭代返回值,它实现了__next____iter__方法,迭代器不会一次性把所有元素加载到内存,而是需要的时候才生成返回结果。
  • 生成器是一种特殊的迭代器,它的返回值不是通过return而是用yield

猜你喜欢

转载自blog.csdn.net/showgea/article/details/81485240