Python可迭代对象/迭代器/生成器

Python可迭代对象/迭代器/生成器

概述

迭代是数据处理的基石.

扫描内存中放不下的数据集时, 需要找到一种惰性获取数据的方式, 即需要按需一次获取一个数据项. 这就是迭代器模式(Iterator pattern).

所有生成器都是迭代器, 因为生成器完全实现了迭代器接口.

在Python中所有集合都可迭代的. 在Python语言内部, 迭代器用于支持:

  • for循环
  • 构建和扩展集合类型
  • 逐行遍历文本文件
  • 列表推导/字典推导/集合推导
  • 元组拆包
  • 调用函数时, 使用*拆包实参

2.序列为何可以迭代?

解释器需要迭代对象x时, 会自动调用iter(x)函数.

内置的iter函数有以下作用:

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

由上可知: 所有的序列都可迭代的原因---都实现了__getitem__方法.

标准的序列也都实现了__iter__方法, 因此我们也应该这么做. 之所以对__getitem__方法做特殊处理,是为了向后兼容.

鸭子类型(duck typing)的极端形式: 不仅要实现特殊的__iter__方法,还要实现__getitem__方法, 而且__getitem__方法的参数是从0开始的整数(int), 才认为对象是可迭代的.

白鹅类型(goose-typing)理论中: 如果实现了__iter__方法, 那么就认为对象是可迭代的.

检查对象x是否可迭代, 最准确的方法: 调用iter(x)函数, 如果不可迭代将抛出TypeError异常, 再来处理此异常.

可迭代的对象与迭代器的对比

可迭代的对象: 使用iter内置函数可以获取迭代器的对象.

  • 如果对象实现了能返回迭代器__iter__方法.
  • 序列对象.
  • 实现了__getitem__方法, 而且其参数是从零开始的索引.

可迭代对象与迭代器关系: Python从可迭代对象中获取迭代器.

StopIteration异常表明迭代器到头了.

Python语言内部会处理for循环和其他迭代器上下文(如列表推导/元组拆包,等等)中的StopIteration异常.

标准迭代器接口:

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

我们应该避免调用类似上述这种特殊方法, 使用next()即可.

检查对象x是否是迭代器最好的办法: 调用isinstance(x, abc.Iterator). 得益于Iterator.__subclasshook__方法, 即使对象x所属的类不是Iterator类的真实子类或虚拟子类, 也能这样检查.

迭代器: 实现了无参数的__next__方法, 返回序列中的下一个元素; 如果没有元素了, 那么抛出StopIteration异常.

Python中的迭代器实现了__iter__方法, 因此迭代器也可以迭代.

因为内置的iter()函数对序列做特殊处理. 接下来实现标准的迭代器协议

3.典型迭代器

# -*- coding: UTF-8 -*-
import re
import reprlib

RE_WORD = re.compile('\w+')


class Sentence:

    def __init__(self, text):
        self.text = text
        self.words = RE_WORD.findall(text)

    def __repr__(self):
        return 'Sentence(%s)' % reprlib.repr(self.text)

    def __iter__(self):
        # 实现此方法: 表明此类可以迭代. 根据迭代器协议,__iter__方法实例并返回一个迭代器
        return SentenceIterator(self.words)


class SentenceIterator:

    def __init__(self, words):
        self.words = words  # 迭代器类引用单词列表
        self.index = 0  # index用于确定下一个要获取的单词

    def __next__(self):
        try:
            word = self.words[self.index]
        except IndexError:
            raise StopIteration()
        self.index += 1
        return word

    def __iter__(self):
        return self


if __name__ == '__main__':
    s = Sentence('"The time has come, " the Walrus said,')
    print(s)
    for word in s:
        print(word)
    print(list(s))

注意: SentenceIterator类的大多数代码都在处理迭代器内部状态.

可迭代对象实现了__iter__方法, 每次都会实例化一个新的迭代器.

迭代器要实现__next__方法, 调用next()函数时返回单个元素, 还要实现__iter__方法, 返回迭代器本身.

因此: 迭代器可以迭代, 但是可迭代的对象本身不是迭代器.

反模式: 在Sentence类中实现__next__方法, 让其实例既是可迭代的对象, 也是自身的迭代器.

可迭代的对象一定不能是自身的迭代器.

也就是: 可迭代的对象必须实现__iter__方法, 但不能实现__next__方法.

另一方面: 迭代器应该可以一直迭代. 迭代器的__iter__方法应该返回自身.

迭代器模式可用来:

  • 访问一个聚合对象的内容而无需暴露它的内部表示
  • 支持对聚合对象的多种遍历
  • 为遍历不同的聚合结构提供一个统一的接口(即支持多态迭代)

为了"支持多种遍历", 必须能从同一个可迭代的实例中获取多个独立的迭代器, 而且各个迭代器要能维护自身的 内部状态, 因此这一模式正确的实现方法是: 每次调用iter(my_iterable)都新建一个独立的迭代器.

这就是定义SentenceIterator类的原因.

4.生成器函数

# -*- coding: UTF-8 -*-
import re
import reprlib

RE_WORD = re.compile('\w+')


class Sentence:

    def __init__(self, text):
        self.text = text
        self.words = RE_WORD.findall(text)

    def __repr__(self):
        return 'Sentence(%s)' % reprlib.repr(self.text)

    def __iter__(self):
        """生成器函数"""
        for word in self.words:
            yield word
        return  # 此return不是必须的.可以直接不写,直接返回None.不管有没有,都不会抛出StopIteration异常.
    

if __name__ == '__main__':
    s = Sentence('"The time has come, " the Walrus said,')
    print(s)
    for word in s:
        print(word)
    print(list(s))

生成器函数: 只要Python函数的定义体中有yield关键字, 该函数就是生成器函数.

调用生成器函数时, 会返回一个生成器对象. 即: 生成器函数是生成器工厂.

执行过程: 生成器函数会创建一个生成器对象, 包装生成器函数的定义体. 把生成器传给next()函数时, 生成器函数会向前, 执行函数定义体中的下一个yield语句, 返回产出的值, 并在函数定义体的当前位置暂停. 最终, 函数定义体返回时, 外层的生成器对象会抛出StopIteration异常---与迭代器协议一致.

In [1]: def gen_123(): 
   ...:     yield 1 
   ...:     yield 2 
   ...:     yield 3 
   ...:                                                                                                                                   

In [2]: gen_123                                                                                                                           
Out[2]: <function __main__.gen_123()>

In [3]: gen_123()                                                                                                                         
Out[3]: <generator object gen_123 at 0x10ce05c50>

In [4]: for i in gen_123(): 
   ...:     print(i) 
   ...:                                                                                                                                   
1
2
3

In [5]: g = gen_123()                                                                                                                     

In [6]: next(g)                                                                                                                           
Out[6]: 1

In [7]: next(g)                                                                                                                           
Out[7]: 2

In [8]: next(g)                                                                                                                           
Out[8]: 3

In [9]: next(g)                                                                                                                           
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-9-e734f8aca5ac> in <module>
----> 1 next(g)

StopIteration: 

In [10]:       

生成器不会以常规的方式"返回"值: 生成器函数定义体中的return语句会触发生成器对象抛出StopIteration异常.

In [10]: def gen_AB(): 
    ...:     print("start") 
    ...:     yield 'A' 
    ...:     print("continue") 
    ...:     yield 'B' 
    ...:     print("end.") 
    ...:                                                                                                                                  

In [11]: for c in gen_AB(): 
    ...:     print('-->', c) 
    ...:                                                                                                                                  
start
--> A
continue
--> B
end.

In [12]: 

for机制的作用与g = iter(gen_AB())一样, 用于获取生成器对象, 然后每次迭代时调用next(g).

5.惰性实现

设计Iterator接口时,考虑到了惰性: next(Iterator)一次生成一个元素.

惰性求值(lazy evaluation)

及早求值(eager evaluation)

# -*- coding: UTF-8 -*-
import re
import reprlib

RE_WORD = re.compile('\w+')


class Sentence:

    def __init__(self, text):
        self.text = text

    def __repr__(self):
        return 'Sentence(%s)' % reprlib.repr(self.text)

    def __iter__(self):
        # finditer函数构建一个迭代器,包含self.text中匹配RE_WORD的单词,产出MatchObject实例
        for match in RE_WORD.finditer(self.text):
            # match.group()方法从MatchObject实例中提取匹配正则表达式的具体文本
            yield match.group()


if __name__ == '__main__':
    s = Sentence('"The time has come, " the Walrus said,')
    print(s)
    for word in s:
        print(word)
    print(list(s))

6.生成器表达式

生成器表达式可以理解为列表推导的惰性版本: 不会马上创建列表, 而是返回一个生成器, 按需惰性生成元素.

即: 如果列表推导是制造列表的工厂, 那么生成器表达式就是制造生成器的工厂.

In [1]: def gen_AB(): 
   ...:     print('start') 
   ...:     yield 'A' 
   ...:     print('continue') 
   ...:     yield 'B' 
   ...:     print('end.') 
   ...:                                                                                                                                   

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

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

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

In [5]: res2                                                                                                                              
Out[5]: <generator object <genexpr> at 0x110f66258>

In [6]: for i in res2: # for循环每次迭代时,会隐式调用next(res2),前进到gen_AB函数的下一个yield语句
   ...:     print('-->', i) 
   ...:                                                                                                                                   
start
--> AAA
continue
--> BBB
end.

由上可知,生成器表达式会产出生成器, 因此可以使用生成器表达式进一步减少Sentence类的代码, 如下所示:

# -*- coding: UTF-8 -*-
import re
import reprlib

RE_WORD = re.compile('\w+')


class Sentence:

    def __init__(self, text):
        self.text = text

    def __repr__(self):
        return 'Sentence(%s)' % reprlib.repr(self.text)

    def __iter__(self):
        return (match.group() for match in RE_WORD.finditer(self.text))


if __name__ == '__main__':
    s = Sentence('"The time has come, " the Walrus said,')
    print(s)
    for word in s:
        print(word)
    print(list(s))

生成器表达式是语法糖: 完全可以替换成生成器函数, 不过有时使用生成器函数更便利.

7.何时使用生成器表达式

如果生成器表达式要分成多行写, 可以选择定义生成器函数, 以便提高可读性.

而且, 生成器函数有名称, 可以重用.

句法提示: 如果函数或构造方法只有一个参数, 传入生成器表达式时不用写一对调用函数的括号, 再写一对括号围住生成器表达式, 只写一对括号就行了. 然而, 如果生成器表达式后面还有其他参数, 那么必须使用括号围住,否则会抛出SyntaxError异常.

8.等差数列生成器

典型的迭代器模式作用很简单-----遍历数据结构.

不过, 即便不是从集合中获取元素, 而是获取序列中即时生成的下一个值时, 也用得到这种基于方法的标准接口.

例如: 内置的range函数用于生成有穷整数等差数列(Arithmetic Progression, AP); itertools.count函数用于生成无穷等差数列.

# -*- coding: UTF-8 -*-


class ArithmeticProgression:

    def __init__(self, begin, step, end=None):
        self.begin = begin
        self.step = step
        self.end = end

    def __iter__(self):
        # 将self.begin的值赋值给result,不过先强制转换成前面的加法算式得到的类型.
        result = type(self.begin + self.step)(self.begin)
        forever = self.end is None
        index = 0
        while forever or result < self.end:
            yield result
            index += 1
            result = self.begin + self.step * index
            
            
if __name__ == '__main__':
    ap = ArithmeticProgression(0, 1, 3)
    print(list(ap))

    ap = ArithmeticProgression(1, .5, 3)
    print(list(ap))

    ap = ArithmeticProgression(0, 1/3, 1)
    print(list(ap))

    from fractions import Fraction
    ap = ArithmeticProgression(0, Fraction(1, 3), 1)
    print(list(ap))

    from decimal import Decimal
    ap = ArithmeticProgression(0, Decimal('.1'), .3)
    print(list(ap))

上面这个类只是演示了如何使用生成器函数实现特殊的__iter__方法.

然而, 如果一个类只是为了构建生成器而去实现__iter__方法, 还不如使用生成器函数.

毕竟, 生成器函数是制造生成器的工厂.

# -*- coding: UTF-8 -*-


def aritprog_gen(begin, step, end=None):
    result = type(begin + step)(begin)
    forever = end is None
    index = 0
    while forever or result < end:
        yield result
        index += 1
        result = begin + step * index


if __name__ == '__main__':
    ap = aritprog_gen(0, 1, 3)
    print(list(ap))

    ap = aritprog_gen(1, .5, 3)
    print(list(ap))

    ap = aritprog_gen(0, 1/3, 1)
    print(list(ap))

    from fractions import Fraction
    ap = aritprog_gen(0, Fraction(1, 3), 1)
    print(list(ap))

    from decimal import Decimal
    ap = aritprog_gen(0, Decimal('.1'), .3)
    print(list(ap))

上述即使用生成器函数来实现的.

使用itertools模块生成等差数列

itertools.count(start, step)示例:

In [7]: import itertools                                                                                                                  

In [8]: gen = itertools.count(1, .5)                                                                                                      

In [9]: next(gen)                                                                                                                         
Out[9]: 1

In [10]: next(gen)                                                                                                                        
Out[10]: 1.5

In [11]: next(gen)                                                                                                                        
Out[11]: 2.0

In [12]: next(gen)                                                                                                                        
Out[12]: 2.5

然而, itertools.count函数从不停止. 如果调用list(count()), Python会创建一个特别大的列表, 超出可用内存, 在调用失败之前, 电脑会疯狂地运转.

itertools.takewhile函数则不同. 它会生成一个使用另一个生成器的生成器, 在指定的条件计算结果为False时停止. 因此, 可以把这两个函数结合在一起使用. 如下所示:

In [13]: gen = itertools.takewhile(lambda n: n < 3, itertools.count(1, .5))                                                               

In [14]: list(gen)                                                                                                                        
Out[14]: [1, 1.5, 2.0, 2.5]

利用takewhilecount函数, 编写的代码流畅而简短, 如下所示:

# -*- coding: UTF-8 -*-
import itertools


def aritprog_gen(begin, step, end=None):
    first = type(begin + step)(begin)
    ap_gen = itertools.count(first, step)
    if end is not None:
        ap_gen = itertools.takewhile(lambda n: n < end, ap_gen)
    return ap_gen


if __name__ == '__main__':
    ap = aritprog_gen(0, 1, 3)
    print(list(ap))

    ap = aritprog_gen(1, .5, 3)
    print(list(ap))

    ap = aritprog_gen(0, 1/3, 1)
    print(list(ap))

    from fractions import Fraction
    ap = aritprog_gen(0, Fraction(1, 3), 1)
    print(list(ap))

    from decimal import Decimal
    ap = aritprog_gen(0, Decimal('.1'), .3)
    print(list(ap))

上述示例中的aritprog_gen不是生成器函数, 因为定义体中并没有yield关键字.

但是它会返回一个生成器, 因此和其他生成器函数一样, 也是生成器工厂函数.

注意: 实现生成器时要知道标准库中有什么可用, 否则很可能重复造轮子.

9.标准库中的生成器函数

第一组是用于过滤的生成器函数:从输入的可迭代对象中产出元素的子集,而且不修改元素本身.

下表中的大多数函数都接受一个断言参数(predicate). 这个参数是个布尔函数, 有一个参数, 会应用到输入中的每个元素上, 用于判断元素是否包含在输出中.

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

示例如下:

In [15]: def vowel(c): 
    ...:     return c.lower() in 'aeiou' 
    ...:                                                                                                                                  

In [16]: list(filter(vowel, 'Aardvark'))                                                                                                  
Out[16]: ['A', 'a', 'a']

In [17]: import itertools                                                                                                                 

In [18]: list(itertools.filterfalse(vowel, 'Aardvark'))                                                                                   
Out[18]: ['r', 'd', 'v', 'r', 'k']

In [19]: list(itertools.dropwhile(vowel, 'Aardvark'))                                                                                     
Out[19]: ['r', 'd', 'v', 'a', 'r', 'k']

In [20]: list(itertools.takewhile(vowel, 'Aardvark'))                                                                                     
Out[20]: ['A', 'a']

In [21]: list(itertools.compress('Aardvark', (1,0,1,1,0,1)))                                                                              
Out[21]: ['A', 'r', 'd', 'a']

In [22]: list(itertools.islice('Aardvark', 4, 7))                                                                                         
Out[22]: ['v', 'a', 'r']

In [23]: list(itertools.islice('Aardvark', 1, 7, 2))                                                                                      
Out[23]: ['a', 'd', 'a']

下一组是用于映射的生成器函数: 在输入的单个可迭代对象(mapstarmap函数处理多个可迭代的对象)中的各个元素上做计算, 然后返回结果.

下表中的生成器函数会从输入的可迭代对象中的各个元素中产出一个元素. 如果输入来自多个可迭代的对象, 第一个可迭代的对象到头后就停止输出.

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

itertools.accumulate示例:

In [24]: sample = [5, 4, 2, 8, 7, 6, 3, 0, 9, 1]                                                                                          

In [25]: import itertools                                                                                                                 

In [26]: list(itertools.accumulate(sample))   # 计算总和                                                                                            
Out[26]: [5, 9, 11, 19, 26, 32, 35, 35, 44, 45]

In [27]: list(itertools.accumulate(sample, min))   # 计算最小值                                                                                       
Out[27]: [5, 4, 2, 2, 2, 2, 2, 0, 0, 0]

In [28]: list(itertools.accumulate(sample, max))   # 计算最大值                                                                                        
Out[28]: [5, 5, 5, 8, 8, 8, 8, 8, 9, 9]

In [29]: import operator                                                                                                                  

In [30]: list(itertools.accumulate(sample, operator.mul))  # 计算乘积                                                                               
Out[30]: [5, 20, 40, 320, 2240, 13440, 40320, 0, 0, 0]

In [31]: list(itertools.accumulate(range(1, 11), operator.mul))   # 计算各数阶乘                                                                         
Out[31]: [1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800]

映射生成器函数示例:

In [32]: list(enumerate('albatroz', 1))  # 以1开始,为单词中的字母编号                                                                                                  
Out[32]: 
[(1, 'a'),
 (2, 'l'),
 (3, 'b'),
 (4, 'a'),
 (5, 't'),
 (6, 'r'),
 (7, 'o'),
 (8, 'z')]

In [33]: import operator                                                                                                                  

In [34]: list(map(operator.mul, range(11), range(11)))  # 从0到10,计算各个数的平方                                                                                  
Out[34]: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

In [35]: list(map(operator.mul, range(11), [2, 4, 8]))  # 元素最少的可迭代对象到头后停止                                                                                  
Out[35]: [0, 4, 16]

In [36]: list(map(lambda x, y: (x, y), range(11), [2, 4, 8])) # 相当于zip函数                                                                            
Out[36]: [(0, 2), (1, 4), (2, 8)]

In [37]: list(itertools.starmap(operator.mul, enumerate('albatroz', 1)))                                                                  
Out[37]: ['a', 'll', 'bbb', 'aaaa', 'ttttt', 'rrrrrr', 'ooooooo', 'zzzzzzzz']

In [38]: sample = [5, 4, 2, 8, 7, 6, 3, 0, 9, 1]                                                                                          

In [39]: list(itertools.starmap(lambda a, b: b/a, enumerate(itertools.accumulate(sample), 1)))                                            
Out[39]: 
[5.0,
 4.5,
 3.6666666666666665,
 4.75,
 5.2,
 5.333333333333333,
 5.0,
 4.375,
 4.888888888888889,
 4.5]

下面一组是用于合并的生成器函数, 这些函数都从输入的多个可迭代对象中产出元素.

  • chainchain.from_iterable按顺序(一个接一个)处理输入的可迭代对象
  • product/zipzip_longest并行处理输入的各个可迭代对象.
模块 函数 说明
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填充.

zip函数的名称出自zip fastenerzipper(拉链, 与ZIP压缩没有关系).

chainzip生成器函数及其同胞使用示例:

In [40]: import itertools                                                                                                                 

In [41]: list(itertools.chain('ABC', range(2))) # 通常传入两个或多个可迭代对象                                                                                          
Out[41]: ['A', 'B', 'C', 0, 1]

In [42]: list(itertools.chain(enumerate('ABC')))  # 如果仅传入一个,则没什么效果                                                                                        
Out[42]: [(0, 'A'), (1, 'B'), (2, 'C')]

In [43]: list(itertools.chain.from_iterable(enumerate('ABC')))                                                                          
Out[43]: [0, 'A', 1, 'B', 2, 'C']

In [44]: list(zip('ABC', range(5)))  # 常用于将两个可迭代对象合并成一系列由两个元素组成的元组                                                                                                      
Out[44]: [('A', 0), ('B', 1), ('C', 2)]

In [45]: list(zip('ABC', range(5), [10, 20, 30, 40]))                                                                                     
Out[45]: [('A', 0, 10), ('B', 1, 20), ('C', 2, 30)]

In [46]: list(itertools.zip_longest('ABC', range(5)))                                                                                     
Out[46]: [('A', 0), ('B', 1), ('C', 2), (None, 3), (None, 4)]

In [47]: list(itertools.zip_longest('ABC', range(5), fillvalue='?'))                                                                      
Out[47]: [('A', 0), ('B', 1), ('C', 2), ('?', 3), ('?', 4)]

10.yield from语法

如果生成器函数需要产出另一个生成器生成的值, 传统解决方法是使用嵌套for循环.

例如, 自己实现itertools.chain:

In [1]: def chain(*iterable): 
   ...:     for it in iterable: 
   ...:         for i in it: 
   ...:             yield i 
   ...:                                                                                                                                   

In [2]: s = 'ABC'                                                                                                                         

In [3]: t = tuple(range(3))                                                                                                               

In [4]: list(chain(s, t))                                                                                                                 
Out[4]: ['A', 'B', 'C', 0, 1, 2]

如果使用yield from语法, 则是如下形式:

In [1]: def chain(*iterable): 
   ...:     for i in iterable: 
   ...:         yield from i 
   ...:                                                                                                                                   

In [2]: s = 'ABC'                                                                                                                         

In [3]: t = tuple(range(3))                                                                                                               

In [4]: list(chain(s, t))                                                                                                                 
Out[4]: ['A', 'B', 'C', 0, 1, 2]

yield from完全代替了内层的for循环.

除了代替for循环外, yield from还会创建通道, 把内层生成器直接与外层生成器的客户端联系起来.

把生成器当成协程使用时, 这个通道特别重要, 不仅能为客户端代码产生值, 还能使用客户端代码提供的值.

11.可迭代的归约函数

归约函数(合拢函数/累加函数): 接受一个可迭代对象, 返回单个结果.

其实,这里每个列出的函数都可以使用itertools.reduce函数实现, 内置是因为它们便于解决常见的问题.

此外对于allany函数来说, 有一项优化措施是reduce函数做不到的: 短路(即一旦确定了结果就立即停止使用迭代器).

模块 函数 说明
内置 all(it) it中所有元素为真时返回True, 否则返回False; all([])返回True
内置 any(it) 只要it中有元素为真值就返回True, 否则返回False; any([])返回False
内置 max(it, [key=, ][default=]) 返回it中最大的元素; key是排序函数, 与sorted函数中的一样; 如果可迭代的对象为空, 返回default
内置 min(it, [key=, ][default=]) 返回it中最小的元素; 其他同上
functools reduce(func, it, [initial]) 把前两个元素传给func, 然后把计算结果和第三个元素传给func, 以此类推, 返回最后的结果; 如果提供了initial, 把他当做第一个元素传入
内置 sum(it, start=0) it中所有元素的和. 如果提供可选的start, 会把它加上

12.深入分析iter函数

鲜为人知的用法: 传入两个参数, 使用常规的函数或任何可调用的对象创建迭代器.

  • 第一个参数必须是可调用的对象, 用于不断调用(没有参数)产出各个值
  • 第二个参数是哨符, 是个标记值, 当可调用的对象返回这个值时, 触发迭代器抛出StopIteration异常, 而不产出哨符.

示例如下:

In [1]: from random import randint                                                                                                        

In [2]: def d6(): 
   ...:     return randint(1, 6) 
   ...:                                                                                                                                   

In [3]: d6_iter = iter(d6, 1)                                                                                                             

In [4]: d6_iter                                                                                                                           
Out[4]: <callable_iterator at 0x108c05c50>

In [5]: for roll in d6_iter: 
   ...:     print(roll) 
   ...:                                                                                                                                   
5

In [6]:  

内置函数iter文档中给了一个很好的例子: 逐行读取文件, 直到遇到特定的行为止.

with open('mydata.txt') as fp:
    for line in iter(fp.readline, ''):
        process_line(line)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,165评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,503评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,295评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,589评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,439评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,342评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,749评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,397评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,700评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,740评论 2 313
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,523评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,364评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,755评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,024评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,297评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,721评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,918评论 2 336

推荐阅读更多精彩内容