Python 函数式编程之迭代器、生成器及其应用

python 标准库中提供了 itertools, functools, operator 三个库支持函数式编程,对高阶函数的支持,python 提供 decorator 语法糖。 迭代器 (iterator)和生成器(generator)概念是 python 函数式编程的基础,利用迭代器和生成器可以实现函数式编程中经常用到的 map(), filter(), reduce() 等过程以及 itertools, functools 中提供的绝大部分功能。

1、迭代器和生成器基础(next, iter, yield)

迭代器和生成器依赖于 next(), iter() 方法和 yield 表达式

1.1 next 函数

next(iterator[, default]) 是内置的函数,通过调用 __next__() 方法取得 iterator 的下一个元素,所有元素消耗完再调用就会引起 StopIteration 异常。如果提供了 default 参数,则当取完所有元素后,再调用 next 时会返回 default 值,而不是引起 StopIteration 异常。

1.2 iter 函数

iter(object[, sentinel]) 内置函数会返回一个迭代器。没有第2个参数时, object 必须支持迭代协议(__iter__() 方法) 或序列协议 (__getitem__() 方法),否则会引起 TypeError 异常。如果有哨兵 (sentinel)参数, object 必须是可调用的对象,这种方式创建的迭代器每次调用 __next__() 方法时会以无参的形式调用 object ,如果返回值等于哨兵就会引起 StopIteration 异常,否则就返回这个值

with open('mydata.txt') as fp:
    for line in iter(fp.readline, ''):
        process_line(line)

1.3 yield 表达式

yield 英文意思是生产,是 python 的关键字,在函数返回值时用来替换 return 产生一个值。yield 表达式只能用于定义生成器函数中,在函数外使用 yield 会导致 SyntaxError: ‘yield’ outside function 。

生成器控制生成器函数的执行,当调用生成器的某个函数时,开始执行,遇到第一个 yield 表达式时,返回 yield 后面表达式的值,然后被挂起(suspend),挂起时保持所有的局部状态,包括局部变量绑定、指令指针、内部的求值栈、异常处理状态;当再次调用生成器的某个方法时,执行流会恢复。

所以生成器函数非常像协程(coroutine,其它语言中的概念),两者都会 yield 多次,有多个入口,执行流会被挂起。唯一的区别是生成器函数不能控制 yield 之后,执行流应该从哪继续,控制总是被转移到生成器的调用者,所以又被称为半协程(semicoroutine)。

2、迭代器(iterator)

迭代器(iterator)必须至少要定义 __iter__() 和 __next__() 两个方法,通过 iter() 和 next() 函数调用。 iter() 生成一个迭代器, next() 每调用一次都会返回下一个值,如果已经到最后一个值了,那么再调用 next() 就会引起 StopIteration 异常。

#python的迭代器类需要实现__iter__魔法方法返回迭代器实例,还需要实现一个next方法,在迭代到末尾时抛出StopIteration异常表示迭代结束。如下简单示例:
class SimpleIterator:
    def __init__(self, maxvalue):
        self.current = 0
        self.max = maxvalue

    def next(self):
        result = self.current
        self.current += 1
        if result == self.max:
            raise StopIteration()
        return result

    def __iter__(self):
        return self


li = list(SimpleIterator(5))
print li

################################

class Reverse:
    """Iterator for looping over a sequence backwards."""
    def __init__(self, data):
        self.data = data
        self.index = len(data)
    def __iter__(self):
        return self
    def __next__(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]

for 循环实际上就是先将 iter() 作用到容器对象上生成迭代器,然后每次调用 next() ,当引起 StopIteration 时就终止 for 循环。

for element in ['a', 'b', 'c']:
    print(element)

# 等价于:
it = iter(['a', 'b', 'c'])
try:
    while True:
        print(next(it))
except StopIteration:
    pass

 

3、生成器(generator)

生成器其实就是一种特殊的迭代器,它使一种更为高级、更为优雅的迭代器,使用生成器让我们可以以一种更加简洁的语法来定义迭代器,它与普通函数相同,只是返回值时用 yield 而不是 return,局部变量和执行状态在调用之间会自动保存。
让我们先明确以下两点:

  • 任意生成器都是迭代器(反过来不成立)
  • 任意生成器,都是一个可以延迟创建值的工厂
def reverse(data):
    for index in range(len(data)-1, -1, -1):
        yield data[index]

#################################
        
def yield_test(maxvalue):
    i = -1
    while i < maxvalue-1:
        i += 1
        yield i
 
for i in yield_test(10):
    print i

能用 generator(函数) 实现的都可以用 iterator(类)实现,不过生成器 会自动创建 __iter__() 和 __next__() 方法,终止时也会自动引起 StopIteration 异常,因而显得更紧凑。

利用生成器表达式(generator expression)可以不用专门定义一个 generator 函数,直接就地使用。生成器表达式与列表表达式(list comprehension)类似,只不过用的是圆括号而不是方括号,由于生成器只保存上次执行的状态,所以相比列表表达式,生成器表达式占用内存更少。

4、流——序列

生成器本质上相当于函数式编程语言中的流的概念,流表面上看是一个序列,但这个序列不是一次构造出来的,而是在需要时构建,函数式编程语言中流是通过惰性求值实现的,可以看到 python 是通过关键词 yield 实现的。

使用流的概念可以避免命令式程序设计中赋值带来的副作用,同时更加简洁优雅。用序列模拟时间变化,相当于是坐标变换,当我们观察一个正在移动的粒子时,我们说该粒子的位置(状态)正在变化,而从粒子的世界线的观点看,这里就根本不涉及任何变化。

TODO : 补充例子

python3 将 python2 中许多列表改成了迭代器,更加函数式了,例如 range(), zip() 在 python2 中返回列表,而 python3 中返回一个迭代器,由于迭代器只是在需要(next())时取元素而不是一次就构建整个列表,所以可以表示非常大的序列甚至无穷序列。

5、生成器——迭代器方法(generator-iterator method)

生成器——迭代器方法 可以用来控制生成器函数的执行流

  • __next__()

  • send(value) 恢复执行流,并将 value 发送到生成器函数,value 作为当前 yield 表达式的值

  • throw(type[, value[, traceback]]) 在生成器暂停的地方引起 type 类型的异常,并返回生成器函数产生的下一个值

  • close

>>> def echo(value=None):
...     print("Execution starts when 'next()' is called for the first time.")
...     try:
...         while True:
...             try:
...                 value = (yield value)
...             except Exception as e:
...                 value = e
...     finally:
...         print("Don't forget to clean up when 'close()' is called.")
...
>>> generator = echo(1)
>>> print(next(generator))
Execution starts when 'next()' is called for the first time.
1
>>> print(next(generator))
None
>>> print(generator.send(2))
2
>>> generator.throw(TypeError, "spam")
TypeError('spam',)
>>> generator.close()
Don't forget to clean up when 'close()' is called.

6、应用

有了上面迭代器和生成器,就可以实现各种函数式编程了,下面是函数式编程中常用的几个函数,更多例子可以查看 itertools 文档

6.1 map 函数

当 map(function, iterable,…) 接收 n 个 iterable 时,每次在各 iterable 中各取一个元素传给 function 作参数,所以 function 必须能够接收 n 个参数,当各个 iterable 长度不一样时按最短的终止,例如 map(lambda x,y: x+y, [1,2], [3,4], [5,6]) 会报错, map(lambda x,y: x+y, ‘abcd’, ‘def’) 返回的迭代器依次为 ‘ad’, ‘be’, ‘cf’

# 这个实现不好,用到了 zip,不过 zip 也可以通过生成器实现(见后面)
def map(function, *iterables):
    for args in zip(*iterables):
        yield function(*args)

itertools.starmap(function, iterable) 只接收一个 iterable,当 function 接收多个参数时,各个参数是放在元组中的,例如 itertools.starmap(pow, [(2,5), (3,2), (10,3)]) 返回迭代器的值依次为 32, 9, 1000。

def starmap(function, iterable):
    for args in iterable:
        yield function(*args)

6.2 filter 函数

filter(function, iterable) 函数相当于生成器表达式 (item for item in iterable if function(item)) ,没有提供 function 参数时相当于 (item for item in iterable if item)

itertools 中提供 filterfalse(predicate, iterable) 函数, filterfalse(lambda x: x%2, range(10)) 得到 0,2,4,6,8, 的迭代器

def filterfalse(predicate, iterable):
    if predicate i None:
        predicate = bool
    for x in iterable:
        if not predicate(x):
            yield x

6.3 reduce 函数

reduce(function, iterable[, initializer]) 函数将 function 从左到右两个两个地累计作用到 iterable 上,从而将 iterable 归约到一个值,例如 reduce(lambda x, y: x+y, [1,2,3,4]) 会计算 (((1+2)+3)+4),从而得到10。 python3 已经将内置的 reduce 函数移到 functools 模块中了

def reduce(function, iterable, initializer=None):
    it = iter(iterable)
    if initializer is None:
        value = next(it)
    else:
        value = initializer
    for element in it:
        value = function(value, element)
    return value

6.4 枚举函数(enumerate)

enumerate(iterable, start=0) 生成一个枚举迭代器,每次调用 next() 时会返回一个元组,包含计数(从 start 开始)和值(iterable)

seasons = ['Spring', 'Summer', 'Fall', 'Winter']
list(enumerate(seasons)) # => [(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]
list(enumerate(seasons, start=1)) # => [(1, 'Spring'), (2, 'Summer'), (3, 'Fall'), (4, 'Winter')]

可以利用生成器实现 enumerate

def enumerate(sequence, start=0):
    n = start
    for elem in sequence:
        yield n, elem
        n += 1

6.5 zip 函数

zip(*iterables) 返回元组迭代器,iterables 长度不同时,按最短的截断, itertools 模块中有 zip_longest() 函数。

a = [1, 2, 3]
b = [1, 4, 9]
c = [1, 8, 27]
list(zip(a, b, c))
# => [(1,1,1), (2,4,8), (3,9,27)]

利用生成器实现 zip

def zip(*iterables):
    sentinel = object()
    iterators = [iter(it) for it in iterables]
    while iterators:
        result = []
        for it in iterators:
            elem = next(it, sentinel)
            if elem in sentinel:
                return
            result.append(elem)
        yield tuple(result)

6.6 累积器(accumulate)

标准库 itertools 提供 accumulate(iterable[,func]) 函数,将 func 函数作用到 iterable 相邻元素上,累计起来,返回的也是一个迭代器。例如 accumulate([1,2,3,4,5]) 返回迭代器,其值依次为 1, 3, 6, 10, 15,而 accumulate([1, 2, 3, 4, 5], operator.mul) 则返回迭代器的值依次为 1, 2, 6, 24, 120

同样也可以用生成器实现 accumulate

def accumulate(iterable, func=operator.add):
    it = iter(iterable)
    total = next(it)
    yield total
    for element in it:
        total = func(total, element)
        yield total

6.7 循环函数(cycle)

itertools.cycle(iterable) 将 iterable 串起来作为 iterator 返回,是无穷循环。例如 cycle(‘ABCD’) 返回迭代器,其值是 A B C D A B C D A …

利用生成器实现 cycle

def cycle(iterable):
    saved = []
    for element in iterable:
        yield element
        saved.append(element)
    while saved:
        for element in saved:
            yield element

6.8 groupby 函数

利用迭代器实现 groupby

class groupby:
    # [k for k, g in groupby('AAAABBBCCDAABBB')] --> A B C D A B
    # [list(g) for k, g in groupby('AAAABBBCCD')] --> AAAA BBB CC D
    def __init__(self, iterable, key=None):
        if key is None:
            key = lambda x: x
        self.keyfunc = key
        self.it = iter(iterable)
        self.tgtkey = self.currkey = self.currvalue = object()
    def __iter__(self):
        return self
    def __next__(self):
        while self.currkey == self.tgtkey:
            self.currvalue = next(self.it)    # Exit on StopIteration
            self.currkey = self.keyfunc(self.currvalue)
        self.tgtkey = self.currkey
        return (self.currkey, self._grouper(self.tgtkey))
    def _grouper(self, tgtkey):
        while self.currkey == tgtkey:
            yield self.currvalue
            self.currvalue = next(self.it)    # Exit on StopIteration
            self.currkey = self.keyfunc(self.currvalue)

 

7、Refer:

[1] Python 函数式编程

http://dengshuan.me/techs/python-functional.html

[2] Java FP: Java中函数式编程的谓词函数(Predicates)第一部分

http://ifeve.com/functional-style-in-java-with/

[3] itertools — 创建高效迭代器的函数

http://python.usyiyi.cn/python_278/library/itertools.html

[4] itertools — Functions creating iterators for efficient looping (高效循环迭代器创建函数)

http://data.digitser.net/python_3.4.2/zh-CN/library/itertools.html

[5] PYTHON-进阶-ITERTOOLS模块小结

http://wklken.me/posts/2013/08/20/python-extra-itertools.html

[6] (译)Python关键字yield的解释(stackoverflow)

http://pyzh.readthedocs.org/en/latest/the-python-yield-keyword-explained.html

[7] Python yield 使用浅析

http://my.oschina.net/leejun2005/blog/94175

[8] Python函数式编程指南(三):迭代器

http://www.cnblogs.com/huxi/archive/2011/07/01/2095931.html

[9] Python函数式编程指南(四):生成器

http://www.cnblogs.com/huxi/archive/2011/07/14/2106863.html

[10] 可迭代对象 vs 迭代器 vs 生成器

http://python.jobbole.com/86258/

    原文作者:python入门
    原文地址: https://my.oschina.net/leejun2005/blog/510817
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞