乐趣区

关于python:Python函数式编程系列012惰性列表之生成器与迭代器

因为本系列还是基于一些曾经对 Python 有肯定相熟度的读者,所以咱们在此不做十分多的赘述来介绍基本知识了。而是回咱们之前的主题,咱们要用迭代器和生成器实现之前的指数函数。

当然,咱们这里还是须要回到惰性列表是什么这个问题。事实上,回到原来惰性求值的概念,惰性列表的概念其实是「须要时才计算出值」的列表。咱们在调用 iter 的时候,其实对常见的对象并没有特地大的劣势。咱们能够假想,其实 iter 转化 [1, 2, 3, 4] 的后果其实如下:

def yield_list():
    yield 1
    yield 2
    yield 3
    yield 4

惟一的劣势,咱们之前曾经提到过了,就是重复套用函数 fg时,咱们是计算 g(f(x)) 而不是先把列表里每个值套用 f 再套用 g。这里有个极大的劣势,就是提前终止时能够防止没有必要的运算。比方,上面一个for 外面的例子,咱们是为了发现列表 ls 中利用 f 函数后如果后果等于 a 就返回 index 否则返回None

def find_index_apply_f(f, ls, a):
    for i, x in enumerate(ls):
        if f(x) == a:
            return i
        else:
            continue
    return None

>>> find_index_apply_f(lambda x: x + 1, [1, 2, 3, 4, 5], 3)
1

当初,这里提前跳出能够缩小十分多的运算量,然而如果应用一个一般列表却很难,咱们在应用 map 之后必然曾经全都计算了,但如果惰性求值,咱们能够就在须要的时候进行就行。这个是列表操作代替循环必须实现的货色。

第二个惰性列表的最大利用,就是无穷列表,比方上面一个生成器,咱们能够生成一个有限长度的全是 x 的列表。前面咱们会聊到咱们在各种场合中曾经用到了这个形象。

def yield_x_forever(x):
    while True:
        yield x

实现一些罕用的 (惰性) 列表操作

大部分操作迭代器 / 生成器的函数,咱们都能够在 itertoools 中找到。但,咱们这里还是要实现一些十分 函数式 的函数,不便当前的操作:

1. head

head很简略,即取出 (惰性) 列表第一个元素:

head = next

2. take

take的指标是列表前 N 个值,这个能够实现成触发计算(转化成非惰性对象,个别为一个值或者列表)或者不触发计算的版本。上面咱们实现的是触发计算的函数。

def take(n, it):
    """将前 n 个元素固定转为列表"""
    return [x for x in islice(it, n)]

take_curry = lambda n: lambda it: take(n, it)

3. drop

drop则相同是删去前 N 个值。

def drop(n, it):
    """剔除前 n 个元素"""
    return islice(it, n, None)

4. tail

tail是删去 head 后的列表,能够用 drop 实现:

from functools import partial

tail = partial(drop, 1)

5. iterate

iterate是重点要用到的函数,就是通过一个迭代函数还有初始值,实现一个无穷列表:

def iterate(f, x):
    yield x
    yield from iterate(f, f(x))

比方,实现所有正偶数的无穷列表:

positive_even_number = iterate(lambda x: x + 2, 2)

当然,更简略地写法是应用 itertools 外面的 repeataccumulate

def iterate(f, x):
    return accumulate(repeat(x), lambda fx, _: f(fx))

简略实际

例子一:求指数

咱们回到之前求指数的例子中,咱们能够实现惰性列表的版本。

第一个思路,咱们就是间接用 iteratex开始,每次乘以 x,而后取出前n 个值,拿到最初一个:

power = lambda x, n: take(n, iterate(lambda xx: xx * x, x))[-1]

另一个就是学生成一个无穷长度的 x,取出前n 个,相乘来reduce

power = lambda x, n: reduce(
    lambda x, y: x * y, 
    take(n, iterate(lambda _: x, x))
)

当然,咱们还能够用 生成器 生成无穷长列表:

def yield_power(x, init=x):
    yield init
    yield from yield_power(x, init * x)

例子二:查找

咱们回到下面讲解的例子,咱们要找到一个无穷列表中套用 f 后,第一个等于 a 的值的index。如果不是惰性的话,这个必须提前跳出也不可能实现。

def find_a_in_lazylist(f, lls, a):
    return head(filter(lambda x: f(x[1]) == a, enumerat(lls)))[0]

总结

本章回顾了利用 Python 自带的生成器、迭代器实现惰性列表,并展现如何使用这些概念做一些数据操作利用。当然在其中,咱们要粗浅感触到,函数式编程与数据是十分亲热的,它关注数据胜于我的项目构造,这点和对象式编程十分不同。大部分对象式编程的教程偏向于概述分层、构造这些概念,真是因为这个是对象式编程善于的中央。

在我实现的教学我的项目 fppy(点击这里返回github) 中,我用内置的 python 模块实现了一个 LazyList 类,用它能够用链式写法实现下面的所有例子:

power1 = lambda x, n: LazyList.from_iter(x)(lambda xx: x * x).take(n).last
power2 = lambda x, n: LazyList.from_iter(x)(lambda _: x).take(n).reduce(lambda xx, yy: xx * yy)

find_a_in_lazylist = lambda f, lls, a: LazyList(lls)\
    .zip_with(LazyList.from_iter(0)(lambda x: x + 1))\
    .filter(lambda x: f(x[1]) == a)\
    .split_head()[0]
退出移动版