最近一段时间都在研究python里边的一些概念:iterator,iterable和generator.看到这篇文章写的十分不错,基本上讲清楚了这三个东西都是什么以及他们之间的关系.这当然值得记录一下😊. 所谓一图胜千言,这篇文章中的这个关系图让人一目了然. relationships.png

纠结所在

如果对iterator,iterable和generator的概念比较纠结,一般主要是没有区分清楚:

  • container
  • iterable
  • iterator
  • generator
  • generator expression
  • [list,set,dict] comprehension

下面按照原文中讲解的那样,对上边几个概念逐一解释:

container

在python中container是一类用于存放各种元素(item)的容器,并且它能够判断一个item是否在container中.一般来说它会将它内部的元素都存放在内存中,并且能够逐一返回它内部的每个元素.一些比较典型的类型都是container:

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

container相对来说比较容易理解,因为它就像是现实生活中的一些真实容器,用来存放各种东西.并且container可以很容易的判断一个item是否是它的一个元素,几个🌰:

  • 对于list、set、tuple
1
2
3
4
5
6
>>> 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,可以直接判断一个元素是否是它的key
1
2
3
4
5
>>> d = {1: 'foo', 2: 'bar', 3: 'qux'}
>>> assert 1 in d
>>> assert 4 not in d
>>> assert 'foo' not in d  # 'foo' is not a _key_ in the dict
>>> assert 'foo' in d.values()
  • 对于string,可以判断一个字符或者子串是否是这个string的组成部分
1
2
3
4
>>> s = 'foobar'
>>> assert 'b' in s
>>> assert 'x' not in s
>>> assert 'foo' in s  # a string "contains" all its substrings

上边一下例子说明对于container来说,判断一个item是否是它其中的一个元素十分直接、方便.尤其对于string来说,虽然它并没有将它的所有子串都放在内存中,但也是可以通过in来进行子串检测. 但是需要注意的是:上边我们关注的主要是container的成员检测,并且一般来说,container也都可以逐一返回它内部的每个元素,比如:

1
2
3
4
5
6
7
8
9
In [16]: a = [1,2,3,[4,5]]

In [17]: for item in a:
    ...:     print item
    ...:
1
2
3
[4,5]

但能逐一返回container中每个元素并不是因为它是一个container(有点绕😄),这是因为它是可迭代的(iterable).所有的container都是iterable的.正如上边图中所示的那样a container typically is an iterable.

iterable

如上所述,所有的container都是iterable的,但是除了各种类型的container以外,像file或者socket这样类型的对象也都是iterable.与container之间不同的是,container大都是包含着有限个元素的,或者说他们有着明确的长度,但是像file或者socket这样的对象他们内部可能会有不那么确定数量的数据.

所以,一个iterable的对象必须是一个能够逐一返回自身包含元素的对象,而这个对象被称为iterator.

iterable和iterator之间的区别可以通过下边的🌰看出:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
>>> x = [1, 2, 3]
>>> y = iter(x)
>>> z = iter(x)
>>> next(y)
1
>>> next(y)
2
>>> next(z)
1
>>> type(x)
<class 'list'>
>>> type(y)
<class 'list_iterator'>

上边的例子中,x是一个iterable(可迭代对象),y和z是两个不同的iterator(迭代器).y和z能够分别逐一返回x中的每个元素,并且这两个iterator之间不会互相影响自己的状态.

通常情况下,一个iterable class会在内部实现__iter__()__next()__方法,这个过程也可以成为实现了迭代器协议,其中__iter__()方法返回迭代器对象iterator,__next()__实现逐一返回元素的过程.

所以,在使用for item in iterator这样的语句进行遍历的时候,其实执行过程类似于下图: iterable-vs-iterator.png

iterator

所以说,到底iterator是个什么东西?🐍

一个iterator对象可以当作是一个工厂,每次使用next()方法,都会产生一个来自iterator内部的值,并且它能够维护于此相关的内部状态.

iterator内部的元素有时候是有限的,但有时候也可能包含无限个数的元素:

1
2
3
4
5
6
>>> from itertools import count
>>> counter = count(start=13)
>>> next(counter)
13
>>> next(counter)
14

利用itertools中的一些方法,可以利用有限元素的iterator变为无限元素的iterator:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
>>> from itertools import cycle
>>> colors = cycle(['red', 'white', 'blue'])
>>> next(colors)
'red'
>>> next(colors)
'white'
>>> next(colors)
'blue'
>>> next(colors)
'red'

有的时候,可以在无限元素的iterator中分出一个有限元素的iterator:

1
2
3
4
5
6
7
8
9
>>> from itertools import islice
>>> colors = cycle(['red', 'white', 'blue'])  # infinite
>>> limited = islice(colors, 0, 4)            # finite
>>> for x in limited:                         # so safe to use for-loop on
...     print(x)
red
white
blue
red

通过构造一个斐波那契数列来看iterator对内部状态是如何控制的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
>>> class fib:
...     def __init__(self):
...         self.prev = 0
...         self.curr = 1
... 
...     def __iter__(self):
...         return self
... 
...     def __next__(self):
...         value = self.curr
...         self.curr += self.prev
...         self.prev = value
...         return value
...
>>> f = fib()
>>> list(islice(f, 0, 10))
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

可以看到class fib 实现了迭代器协议,所以f是一个iterator,并且是iterable的.通过两个内部变量prevcurr,fib能够在每次相应next()调用的时候返回对应的值.每次对next()的响应,对于iterator来说主要有两件事情要做:

  1. 为下一次next()调用改变内部状态
  2. 生成当前next()调用要返回的值

由此可以看出,iterator可以当作是一个惰性求值的工厂,每次next()调用会让iterator生成所需要的值,然后在下一次next()调用前都保持空闲状态.🕶️

generator

generator(生成器),可以当作一种特殊的iterator(更为优雅的一种). 它能够用更为简洁的代码来实现上边斐波那契数列,并且避免手动实现__iter()____next()__. 举个🍐:

1
2
3
4
5
6
7
8
9
def fib():
    prev,curr = 0,1
    while True:
        yield curr
        prev,curr = curr,curr+prev

>>> f = fib()
>>> list(islice(f, 0, 10))
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

通过yield来构造的generator,是不是更加优雅.💍 通常有两种方法能够构造一个generator:

  1. generator function
  2. generator comprehension

generator function很好理解,表面上和普通的函数没什么区别,只是这个函数通过关键字yield来返回值. 类似于列表推导式或者set、dict,通过(x for x in rang(10))这样的形式返回的就是一个generator,而通过圆括号()进行的推导式就是generator comprehension. 总的来说,generator能够非常有效的组织代码,用更少的变量和流程控制,并且使用generator会更好地减少内存或者cpu的负载.所以像我这样的代码里边遍布了各种for循环:

1
2
3
4
5
def something():
    result = []
    for ... in ...:
        result.append(x)
    return result

还是抓紧时间好好改一改吧:

1
2
3
def iter_something():
    for ... in ...:
        yield x