tongsiying

阅读|运动|自律

0%

第28篇:迭代器和生成器

学习目标

把可迭代对象、迭代器和生成器的概念和用法掌握

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

它们之间的关系可以用下图表示:

关系图

这张图解释了他们之间的从属关系和转换关系

关系图2

  • 可迭代对象:其中实现了__iter__方法的对象就叫做可迭代对象。( 严格来说,可迭代对象是任何实现了 iter() 方法或者实现了序列(Sequence)语义中的 *getitem() *方法的任意自定义类对象。)
  • 迭代器:既实现了__iter__,也实现了__next__方法的对象叫做迭代器;
  • 生成器:有两种,一种是具有yield关键字的函数都是生成器,其二是生成器表达式,隐式的实现了迭代器协议。

一、容器

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

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

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

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中用dict的中key:

1
2
3
>>> d = {1: 'foo', 2: 'bar', 3: 'qux'}
>>> assert 1 in d
>>> assert 'foo' not in d # 'foo' 不是dict中的元素

询问某substring是否在string中:

1
2
3
4
>>> s = 'foobar'
>>> assert 'b' in s
>>> assert 'x' not in s
>>> assert 'foo' in s

尽管绝大多数容器都提供了某种方式来获取其中的每一个元素,但这并不是容器本身提供的能力,而是可迭代对象赋予了容器这种能力。

容器的两个特点

  1. 容器中的元素可通过迭代获取。
  2. 所有容器中的元素被存储在内存中。(不同于其它迭代器得地方)

二、迭代器协议

在讨论可迭代对象、迭代器和生成器之前,先说明一下迭代器模式(iterator pattern),维基百科这么解释:

迭代器是一种最简单也最常见的设计模式。它可以让用户透过特定的接口巡访容器中的每一个元素而不用了解底层的实现。

迭代是数据处理的基石。当内存中放不下数据集时,我们要找到一种惰性获取数据的方式,即按需一次获取一个数据项,这就是迭代器模式。

序列可迭代的原因:iter函数
我们都知道序列是可迭代的。当解释器需要迭代对象x时,会自动调用iter(x)
内置的iter函数有以下作用:

  • 检查对象是否实现了__iter__方法,如果实现了就调用它,获得一个迭代器。
  • 如果没有实现__iter__方法,但是实现了__getitem__方法,python会创建一个迭代器,尝试按顺序(从索引0开始)获取元素。
  • 如果尝试失败,python会抛出TypeError异常,通常会提示”C object is not iterable”,其中C是目标对象所属的类。

截止到Python3.6,基本上所有的Python序列也都实现了__getitem__方法,这是保证任何序列都可迭代的原因。当然标准的序列也都实现了__iter__方法,之所以对__getitem__也可以创建迭代器是为了向后兼容,未来可能不在这么做。

但是,从Python3.4开始,检查x能否迭代,最准确的方法是调用iter(x)函数,如果不可迭代,再处理TypeError异常。

三、可迭代对象

严格来说,可迭代对象是任何实现了__iter__() 方法或者实现了序列(Sequence)语义中的__getitem__()方法的任意自定义类对象。以通俗简单的方式来讲,可迭代对象就是能够逐一返回其成员项的对象,其可用于 for 循环。通过使用iter()方法,我们能将可迭代对象返回成迭代器。例如:

1
2
3
4
5
from collections import Iterable
# 定义一个列表,其本身是可迭代对象
list_a = ['a', 'b', 'c']
print(isinstance(list_a, Iterable)) # True
new_a = iter(list_a)

1. 实现可迭代对象的两种方式

那么下面我们分别来讨论下这两种实现方案。

1.1 实现__iter__

这种方式是你必须为你的类实现iter()方法,该方法返回的必须是一个迭代器对象,下面会讲到。
注意:可迭代对象得iter方法,返回的必须是迭代器,但不一定是self
(这与迭代器的iter不同,因为迭代器的self一定是迭代器,而可迭代对象的self不一定是迭代器)

__iter__方法说明
该方法返回的是当前对象的迭代器类的实例。因为可迭代对象与迭代器都要实现这个方法,因此有以下两种写法。
写法一:用于可迭代对象类的写法,返回该可迭代对象的迭代器类的实例。
写法二:用于迭代器类的写法,直接返回self(即自己本身),表示自身即是自己的迭代器。

例如:pytorchDataloader类,其__iter__方法如下:

1
2
3
4
5
def __iter__(self):
if self.num_workers == 0:
return _SingleProcessDataLoaderIter(self)
else:
return _MultiProcessingDataLoaderIter(self)

1.2 实现getitem

序列:序列(Sequence)的迭代通过实现getitem() 方法来使用整数索引进行高效的元素访问。同时,你必须为其定义一个返回序列长度的len() 方法。例如上面例子中的 list, str, tuple, bytes。序列数据结构的存储是一段连续的内存空间,其直接使用整数索引进行寻址,查找元素非常高效,但是插入删除元素时效率低下。
字典:有的童鞋会说,dict 也实现了getitemlen 协议,为何dict 不属于序列?原因是它并不是通过整数索引来顺序迭代,而是通过任意的不可变键(immutable key) 来进行逐个查找的。所以 dict 的迭代还是因为其实现了iter

2. 工作方式(转换为迭代器的方式)

  • 第一种工作方式:当把一个可迭代对象 x 作为参数传给内置函数 iter() 时,它会返回该对象的迭代器以供迭代。
  • 第二种工作方式:但通常我们并不需要通过iter()函数,而是直接使用 for 对可迭代对象进行遍历,for 语句会为你自动处理那些操作:
    (1)如果实现了iter,则自动调用 iter(x) 来获取迭代器。
    (2) 如果实现了 getitemlen 协议,其会自动创建一个迭代器,并尝试从索引 0 开始获取元素,若尝试失败,会引发一个 TypeError。

3. 类型判断

判断一个对象是否是一个可迭代对象,可以使用 collections.abc.Iterable。

1
2
3
In [1]: from collections.abc import Iterable
In [2]: isinstance([1, 2, 3], Iterable)
Out[2]: True

四、迭代器

即实现了迭代器协议的对象就是一个迭代器,迭代器协议由 iter() 和 next() 共同组成。

  • __iter__必须返回迭代器对象本身。
  • next 应从容器中返回下一项,如果已经没有数据项可返回时,则需引发 StopIteration 异常,继续调用其__next__应再次引发 StopIteration异常。

1. 迭代器的实现

(__iter____next__方法说明)

一个迭代器应有的样子应该是这样的:

1
2
3
4
5
class Iterator:
def __iter__(self):
return self
def __next__(self):
pass

__iter____next__方法说明
1)__iter__
该方法返回的是当前对象的迭代器类的实例。因为可迭代对象与迭代器都要实现这个方法,因此有以下两种写法。
写法一:用于可迭代对象类的写法,返回该可迭代对象的迭代器类的实例。
写法二:用于迭代器类的写法,直接返回self(即自己本身),表示自身即是自己的迭代器。

2)__next__
返回迭代的下一步,实现该方法时注意最后超出边界要抛出StopIteration异常。

2. 迭代器的特点

  • 迭代器是一个带状态的对象,迭代器内部持有一个状态,该状态用于记录当前迭代所在位置,以便于下次迭代的时候获取正确的元素。迭代器可以通过next()方法来迭代获取下一个值。
  • 迭代器不会一次性吧所有元素都加载到内存,而是需要的时候才返回结果。

3. 工作方式

迭代器用来表示一连串数据流的对象,当 for 语句自动返回可迭代对象的迭代器时,for 将重复调用其 next() 方法将逐个返回流中的项,直到迭代器引发 StopIteration 异常后终止循环。此时该迭代器数据项已耗尽,不能再使用。

五、可迭代对象和迭代器关系

使用iter内置函数可以获取迭代器对象。也就是说,如果一个对象实现了能返回迭代器的__iter__方法,那么对象就是可迭代的,序列都可以迭代;实现了__getitem__方法,而且其参数是从零开始的索引,这种对象也是可迭代的。

因此可以明确可迭代对象迭代器之间的关系:Python从可迭代的对象中获取迭代器。

标准的迭代器接口有两个方法,即:

  • __next__:返回下一个可用元素,如果没有元素,抛出StopIteration异常
  • __iter__:返回self,以便在应该使用可迭代对象的地方使用迭代器,比如for循环中。

因为迭代器只需__next____iter__两个方法,所以除了调用next()方法,以及捕获StopIteration异常之外,没有办法检查是否还有遗留的元素。此外,也没有办法还原迭代器。如果想再次迭代,那就要调用iter(…),传入之前构建迭代器的可迭代对象。

构建可迭代对象迭代器时经常会出现错误,原因是混淆了两者。要知道,可迭代的对象有个__iter__方法,调用该方法每次都实例化一个新的迭代器\;而迭代器要实现__next__方法,返回单个元素,此外还要实现__iter__方法,返回迭代器本身(self),如图。因此,迭代器可以迭代,但是可迭代的对象不是迭代器

img

需要注意的是:

可迭代的对象必须实现__iter__方法,但不能\实现__next__方法。另一方面,迭代器应该一直可以迭代,迭代器的__iter__方法应该返回自身。虽然可迭代对象和迭代器都有__iter__方法,但是两者的功能不一样,再次强调一下,可迭代对象的__iter__用于实例化一个迭代器对象,而迭代器中的__iter__用于返回迭代器本身,与__next__共同完成迭代器的迭代作用。

六、生成器

生成器是一种特殊的迭代器,生成器自动实现了“迭代器协议”(即__iter____next__方法),不需要再手动实现两方法。在Python中创建迭代器最方便的方法是使用生成器。生成器也是迭代器。生成器的语法类似于函数,但是不返回值。为了显示序列中的每一个元素,会使用yield语句。只要Python函数的定义体中有yield关键字,该函数就是生成器函数。调用生成器函数时,会返回一个生成器对象。获取生成器通常有两种方式:生成器函数生成器表达式

1. 生成器主要的特点

1、生成器通过关键字yield自动实现了“迭代器协议”(即__iter____next__方法),不需要再手动实现两方法。生成器特殊的地方在于函数体中没有return关键字,函数的返回值是一个生成器对象。当执行f=fab()返回的是一个生成器对象,此时函数体中的代码并不会执行,只有显示或隐示地调用next的时候才会真正执行里面的代码。

2、生成器可以传入数据进行计算(而修改普通迭代器的当前迭代值往往会发生异常),并根据变量内容计算结果后返回。

3、迭代器不会一次把所有的元素加载到内存,而是调用的时候才生成返回结果(这点相同于迭代器)

4、可以通过for循环进行迭代(因为生成器是迭代器)

2. 实现生成器

方式1:生成器函数

和普通函数的 return 返回不同,生成器函数使用 yield。yield可以理解为return,返回后面的值给调用者。不同的是return返回后,函数会释放,而生成器则不会。在直接调用next方法或用for语句进行下一次迭代时,生成器会从yield下一句开始执行,直至遇到下一个yield。生成器特殊的地方在于函数体中没有return关键字,函数的返回值是一个生成器对象。当执行f=fab()返回的是一个生成器对象,此时函数体中的代码并不会执行,只有显示或隐示地调用next的时候才会真正执行里面的代码。

首先,如果你还没有对yield有个初步分认识,那么你先把yield看做“return”,这个是直观的,它首先是个return,普通的return是什么意思,就是在程序中返回某个值,返回之后程序就不再往下运行了。看做return之后再把它看做一个是生成器(generator)的一部分(带yield的函数才是真正的迭代器),好了,如果你对这些不明白的话,那先把yield看做return,然后直接看下面的程序,你就会明白yield的全部意思了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def foo():
print("starting...")
while True:
res = yield 4
print("res:",res)
g = foo()
print(next(g))
print("*"*20)
print(next(g))

"""
starting...
4
********************
res: None
4
"""

我直接解释代码运行顺序,相当于代码单步调试:

  • 1.程序开始执行以后,因为foo函数中有yield关键字,所以foo函数并不会真的执行,而是先得到一个生成器g(相当于一个对象)
  • 2.直到调用next方法,foo函数正式开始执行,先执行foo函数中的print方法,然后进入while循环
  • 3.程序遇到yield关键字,然后把yield想想成return,return了一个4之后,程序停止, 并没有执行赋值给res操作,此时next(g)语句执行完成,所以输出的前两行(第一个是while上面的print的结果,第二个是return出的结果)是执行print(next(g))的结果,
  • 4.程序执行print(“20),输出20个*
  • 5.又开始执行下面的print(next(g)),这个时候和上面那个差不多,不过不同的是,这个时候是从刚才那个next程序停止的地方开始执行的,也就是要执行res的赋值操作,这时候要注意,这个时候赋值操作的右边是没有值的(因为刚才那个是return出去了,并没有给赋值操作的左边传参数),所以这个时候res赋值是None,所以接着下面的输出就是res:None,
  • 6.程序会继续在while里执行,又一次碰到yield,这个时候同样return 出4,然后程序停止,print函数输出的4就是这次return出的4.

到这里你可能就明白yield和return的关系和区别了,带yield的函数是一个生成器,而不是一个函数了,这个生成器有一个函数就是next函数,next就相当于“下一步”生成哪个数,这一次的next开始的地方是接着上一次的next停止的地方执行的,所以调用next的时候,生成器并不会从foo函数的开始执行,只是接着上一步停止的地方开始,然后遇到yield后,return出要生成的数,此步就结束。

1
2
3
4
5
6
7
8
9
def foo():
print("starting...")
while True:
res = yield 4
print("res:",res)
g = foo()
print(next(g))
print("*"*20)
print(g.send(7))

再看一个这个生成器的send函数的例子,这个例子就把上面那个例子的最后一行换掉了,输出结果:

1
2
3
4
5
starting...
4
********************
res: 7
4

先大致说一下send函数的概念:此时你应该注意到上面那个的蓝色字体,还有上面那个res的值为什么是None,这个变成了7,到底为什么,这是因为,send是发送一个参数给res的,因为上面讲到,return的时候,并没有把4赋值给res,下次执行的时候只好继续执行赋值操作,只好赋值为None了,而如果用send的话,开始执行的时候,先接着上一次(return 4之后)执行,先把7赋值给了res,然后执行next的作用,遇见下一回的yield,return出结果后结束。

关键点:

  • 程序执行g.send(7),程序会从yield关键字那一行继续向下运行,send会把7这个值赋值给res变量
  • 由于send方法中包含next()方法,所以程序会继续向下运行执行print方法,然后再次进入while循环
  • 程序执行再次遇到yield关键字,yield会返回后面的值后,程序再次暂停,直到再次调用next方法或send方法。

下面再看几个例子:

  • 示例1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    def gen_123():  # 只要Python代码中包含yield,该函数就是生成器函数
    yield 1 #生成器函数的定义体中通常都有循环,不过这不是必要条件;此处重复使用了3次yield
    yield 2
    yield 3

    if __name__ == '__main__':
    print(gen_123) # 可以看出gen_123是函数对象
    # <function gen_123 at 0x10be19>
    print(gen_123()) # 函数调用时返回的是一个生成器对象
    # <generator object gen_123 at 0x10be31>

    for i in gen_123(): # 生成器是迭代器,会生成传给yield关键字的表达式的值
    print(i)
    # 1
    # 2
    # 3
    g = gen_123() # 为了仔细检查,把生成器对象赋值给g
    print(next(g)) # 1
    print(next(g)) # 2
    print(next(g)) # 3
    print(next(g)) # 生成器函数的定义体执行完毕后,生成器对象会抛出异常。
    # Traceback (most recent call last):
    # File "test.py", line 17, in <module>
    # print(next(g))
    # StopIteration
  • 只要Python代码中包含yield,该函数就是生成器函数

    • 生成器函数的定义体中通常都有循环,不过这不是必要条件;此处重复使用了3次yield
    • 可以看出gen_123是函数对象
    • 函数调用时返回的是一个生成器对象
    • 生成器是迭代器,会生成传给yield关键字的表达式的值
    • 为了仔细检查,把生成器对象赋值给g
    • 因为g是迭代器,所以调用next(g)会获取yield生成的下一个元素
    • 生成器函数的定义体执行完毕后,生成器对象会抛出异常。
  • 示例2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # 1. 定义生成器
    def myList(num):
    now = 0 # 当前迭代值,初始为0
    while now < num:
    val = yield now # 返回当前迭代值,并接受可能的send发送值;
    now = now + 1 if val is None else val # val为None,迭代值自增1,否则重新设定当前迭代值为val

    # 2. 测试代码
    my_list = myList(5) # 得到一个生成器对象
    print(my_list.__next__()) # 返回当前迭代值
    print(my_list.__next__())
    print(my_list.send(3)) # 重新设定当前的迭代值
    print('next', next(my_list))
    print(dir(my_list)) # 返回该对象所拥有的方法名,可以看到__iter__与next在其中
    1
    2
    3
    4
    5
    6
    7
    """
    0
    1
    3
    4
    ['__class__', '__del__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__next__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']
    """

    generator其实有第2种调用方法(恢复执行),即通过send(value)方法将value作为yield表达式的当前值,你可以用该值再对其他变量进行赋值,这一段代码就很好理解了。当我们调用send(value)方法时,generator正由于yield的缘故被暂停了。此时,send(value)方法传入的值作为yield表达式的值,函数中又将该值赋给了变量s,然后print函数打印s,循环再遇到yield,暂停返回。

  • 示例3

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    def odd_func(start=1, end=10):
    for val in range(start, end + 1):
    if val % 2 == 1:
    yield val

    of = odd_func(1, 5)
    print(of) # <generator object odd_func at 0x00000132300C85C8>
    print(next(of)) # 1
    print(next(of)) # 3
    print(next(of)) # 5
    print(next(of))
    """
    Traceback (most recent call last):
    File "D:/python/Python之路/6 生成器迭代器生成式/code/example2.py", line 21, in <module>
    print(next(of))
    StopIteration
    """
  • 示例4

    yeild后面不跟任何值,这时yeild返回的值只能通过send()送进去。但此时首先要调用一次send(None)或者next()方法.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    def gen():
    while True:
    s = yield
    print(s)

    g = gen()

    # g.send('hello') # 这里很重要,向一个刚开始的生成器直接send一个值,会报错,所以我们得先调用next方法
    # Traceback (most recent call last):
    # File "D:/python/Python之路/6 生成器迭代器生成式/code/example3.py", line 16, in <module>
    # g.send('hello')
    # TypeError: can't send non-None value to a just-started generator
    # 让生成器向后移动一个位置后再send值
    print(next(g)) # 或者调用send(None)
    g.send('hello')

    TypeError: can’t send non-None value to a just-started generator告诉我们,刚开始的生成器只能send(None)。调用send(value)时要注意,要确保,generator是在yield处被暂停了,如此才能向yield表达式传值,否则将会报错(如上所示),可通过next()方法或send(None)使generator执行到yield。

方式2:生成器表达式(generator expression)

生成器表达式是列表推倒式的生成器版本,看起来像列表推导式,但是它返回的是一个生成器对象而不是列表对象。

1
2
a = (x for x in range(10))
print(a) # <generator object <genexpr> at 0x000001DB6DD91748>

简单的生成器函数,可以替换成生成器表达式。生成器表达式可以理解为列表推导的惰性版本:不会迫切的构建列表,而是返回一个生成器,按需惰性生成元素。也就是说,如果列表推导是制造工厂的列表,那么生成器表达式就是制造生成器的工厂。如下演示了一个简单的生成器表达式,并且与列表推导做了对比。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
In [1]: def gen_AB():            # 1
...: print('start')
...: yield 'A'
...: print('continue')
...: yield 'B'
...: print('end.')
...:

In [2]: res1 = [x*3 for x in gen_AB()] # 2
start
continue
end.

In [3]: for i in res1(): # 3
...: print('-->', i)
...:
AAA
BBB

In [4]: res2 = (x*3 for x in gen_AB()) # 4

In [5]: res2 # 5
<generator object <genexpr> at 0x106a07620>

In [6]: for i in res2(): # 6
...: print('-->', i)
...:
start
--> A
continue
--> B
end.
  • 创建gen_AB函数
  • 列表推到迫切的迭代gen_AB()函数生成的生成器对象产出的元素:’A’和’B’。注意。下面输出的是start、continue、end.。
  • for循环迭代列表推导生成的res1列表
  • 把生成器表达式返回的值赋值给res2。只需调用gen_AB()函数,虽然调用时会返回一个生成器,但是这里并不使用。
  • 可以看出res2是一个生成器对象。
  • 只有for循环迭代res2时,gen_AB函数的定义体才会真正执行。for循环每次迭代时会隐式调用next(res2),前进到gen_AB函数中的下一个yield语句。注意,gen_AB函数的输出与for循环中print函数的输出夹杂在一起。

生成器表达式是创建生成器的简洁句法,这样无需定义函数再调用。不过,生成器函数灵活的多,可以使用多个语句实现复杂的逻辑,也可以作为协程(后面有机会讲到)使用。遇到简单的情况时,可以使用生成器表达式,因为这样扫一眼就知道代码的作用。其实选择那种句法很容易判断:如果生成器表达式需要分行写,倾向于定义成生成器函数,以便提高可读性;此外生成器函数有名称,因此可以重用。

1
2
3
a = (i for i in range(10))
'__next__' in dir(a) # True
'__iter__' in dir(a) # True

以上可以看出,生成器也是一种迭代器!

前面我们提到了惰性计算。其实,我们有时候使用生成器而不是传统的函数时,正是因为惰性计算有好处—只计算需要的数据,并且整个系列不需要一次性全部驻留在内存中。事实上,生成器完全可以有效的生产数值的无限序列,举一个例子,斐波那契数列是一个经典的无限数字序列,下面用生成器可以产生这个无穷级数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def fibonacci():
a = 0
b = 1
while True:
yield a
future = a + b
a = b
b = future


if __name__ == '__main__':
fib = fibonacci()
for _ in range(10):
print(next(fib))

七、标准库中的生成器函数

1. 用于过滤的生成器函数

模块 函数 说明
itertools compress(it, selector_it) 并行处理两个可迭代的对象;如果 selector_it 中的元素是真值,产出 it 中对应的元素
itertools dropwhile(predicate, it) 处理 it,跳过 predicate 的计算结果为真值的元 素,然后产出剩下的各个元素(不再进一步检 查)
filter(predicate, it) 把 it 中的各个元素传给 predicate,如果 predicate(item) 返回真值,那么产出对应的元 素;如果 predicate 是 None,那么只产出真值元 素
itertools filterfalse(predicate, it) 与 filter 函数的作用类似,不过 predicate 的 逻辑是相反的:predicate 返回假值时产出对应 的元素
itertools islice(it, stop) 或 islice(it, start, stop, step=1) 产出 it 的切片,作用类似于 s[:stop] 或 s[start:stop:step],不过 it 可以是任何可迭代 的对象,而且这个函数实现的是惰性操作
itertools takewhile(predicate, it) predicate 返回真值时产出对应的元素,然后立 即停止,不再继续检查

2. 用于映射的生成器函数

模块 函数 说明
itertools accumulate(it, [func]) 产出累积的总和;如果提供了 func,那么把前两个 元素传给它,然后把计算结果和下一个元素传给 它,以此类推,最后产出结果
enumerate(iterable, start=0) 产出由两个元素组成的元组,结构是 (index, item),其中 index 从 start 开始计数,item 则从 iterable 中获取
map(func, it1, [it2, …, itN]) 把 it 中的各个元素传给func,产出结果;如果传入 N 个可迭代的对象,那么 func 必须能接受 N 个参 数,而且要并行处理各个可迭代的对象
itertools starmap(func, it) 把 it 中的各个元素传给 func,产出结果;输入的 可迭代对象应该产出可迭代的元素 iit,然后以 func(*iit) 这种形式调用 func

3. 合并多个可迭代对象的生成器函数

模块 函数 说明
itertools chain(it1, …, itN) 先产出 it1 中的所有元素,然后产出 it2 中的 所有元素,以此类推,无缝连接在一起
itertools chain.from_iterable(it) 产出 it 生成的各个可迭代对象中的元素,一个 接一个,无缝连接在一起;it 应该产出可迭代 的元素,例如可迭代的对象列表
itertools product(it1, …, itN, repeat=1) 计算笛卡儿积:从输入的各个可迭代对象中获 取元素,合并成由 N 个元素组成的元组,与嵌 套的 for 循环效果一样;repeat 指明重复处理 多少次输入的可迭代对象
zip(it1, …, itN) 并行从输入的各个可迭代对象中获取元素,产 出由 N 个元素组成的元组,只要有一个可迭代 的对象到头了,就默默地停止
itertools zip_longest(it1, …, itN, fillvalue=None) 并行从输入的各个可迭代对象中获取元素,产 出由 N 个元素组成的元组,等到最长的可迭代 对象到头后才停止,空缺的值使用 fillvalue 填充

4. 把输入的各个元素扩展成多个输出元素的生成器对象

模块 函数 说明
itertools combinations(it, out_len) 把 it 产出的 out_len 个元素组合在 一起,然后产出
itertools combinations_with_replacement(it, out_len) 把 it 产出的 out_len 个元素组合在 一起,然后产出,包含相同元素的 组合
itertools count(start=0, step=1) 从 start 开始不断产出数字,按 step 指定的步幅增加
itertools cycle(it) 从 it 中产出各个元素,存储各个元 素的副本,然后按顺序重复不断地 产出各个元素
itertools permutations(it, out_len=None) 把 out_len 个 it 产出的元素排列在 一起,然后产出这些排列;out_len的默认值等于 len(list(it))
itertools repeat(item, [times]) 重复不断地产出指定的元素,除非 提供 times,指定次数

5. 用于重新排列元素的生成器函数

模块 函数 说明
itertools groupby(it,key=None) 产出由两个元素组成的元素,形式为 (key, group),其中 key 是分组标准,group 是生成器, 用于产出分组里的元素
reversed(seq) 从后向前,倒序产出 seq 中的元素;seq 必须是序 列,或者是实现了 reversed 特殊方法的对象
itertools tee(it, n=2) 产出一个由 n 个生成器组成的元组,每个生成器 用于单独产出输入的可迭代对象中的元素

八、简单小结

  • iterable对象:可迭代对象,实现了返回迭代器的__iter__()方法或者__gititem__()方法且其参数是从0开始的索引。包含容器、迭代器等。
  • container对象:容器对象,所有容器中的元素被存储在内存中。像list、tuple、str、dict等对象。
  • iterator对象:迭代器对象,不同于container的数据存储形式,iterator只存储算法的内存地址,iterator对象与iterable对象不同之处在于,iterator同时具有__iter__()__next__()方法。尽管像列表、元祖这样的对象不具有__next__()方法,但是可以通过iter()方法或者for语句为它们返回一个迭代器对象。
  • generator对象:生成器对象是特殊的迭代器,具有迭代器一切的特点。创建generator对象一般有两种方法,一种是生成器表达式,形式上与列表、集合解析式很像,实质却完全不同;另一种创建方式yield生成器函数。
赞赏一下吧~