本篇文章是基于Joel Grus: Learning Data Science Using Functional Python视频的笔记。
常用的函数
currying
在Python中实现科里化的最佳方案是functools.partial
。例如以下例子:
# 一般版本
def add1(x): return add(1, x)
# FP的版本
add1_functional = partial(add, 1)
reduce、map、filter
这几个是常见的FP中处理列表的函数,在此不做介绍。
注意:Python这得
reduce
在functools
包中。
iterators(迭代器)
以下是个迭代器的例子:
In [4]: a = [1,3,4]
In [5]: b = iter(a)
In [6]: next(b)
Out[6]: 1
In [7]: next(b)
Out[7]: 3
In [8]: next(b)
Out[8]: 4
In [9]: next(b)
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-9-641a931447e8> in <module>()
----> 1 next(b)
StopIteration:
迭代器的特点主要包括:
一次只用一个
只有需要时才会产生数据。
这两个特点保证了其惰性的特点,而另一个好处是我们可以构造无穷序列。
generator生成器
生成器所要生成的结果其实就是迭代器,如下:
def lazy_integers(n = 0):
while True:
yield n
n += 1
xs = lazy_integers()
[next(xs) for _ in range(10)]
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[next(xs) for _ in range(10)]
# [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
上面的例子我们可以看出,生成器在此生成了一个无穷长度的序列,由于其惰性我们可以在有限内存之中储存这个无穷序列。
但值得注意的是,因为迭代器中的每个值只能使用一次,就会得到同样语句不同结果的例子(见上)。
一个高阶的用法
squares = (x ** 2 for x in lazy_integers())
doubles = (x * x for x in lazy_integers())
next(squares) #0
next(squares) #1
next(squares) #4
我们发现使用tuple可以直接改变迭代器中的每个元素,这个特性很方便;但值得注意的是,不能写成列表的形式,不然输出值就不为惰性迭代器,就会引起内存外溢。
生成器模拟pipeline
考虑一个文件之中出现某个单词(例如“prime”)的句子个数,采用函数式的方法,显然,如下:
with open("a.txt", "r") as f:
lines = (line for line in f)
prime_lines = filter(lambda line: "prime" in line.lower(), lines)
line_count = len(list(prime_lines))
itertools模块
itertools
模块提供了大量用于操作迭代器的函数。
函数名 | 参数 | 作用 |
---|---|---|
count | [start=0], [step=1] | 输出无穷序列(start, start + step, start + 2 * step...) |
islice | seq, [start=0], stop, [step=1] | 输出序列的切片 |
tee | it, [n=2] | 复制序列,输出为多个相同序列组成的元组 |
repeat | elem, [n=forever] | 重复序列n次,输出为一个repeat元素 |
cycle | p | 无限重复cycle里的元素 |
chain | p, q, ... | 迭代p里的元素,然后再迭代q的元素,... |
accumulate | p, [func=add] | 返回(p[0], func(p[0], p[1]), func(func(p[0], p[1]), p[2])...) |
自定义一些常用的迭代工具
def take(n, it):
"""
将前n个元素固定转为列表
"""
return [x for x in islice(it, n)]
def drop(n, it):
"""
剔除前n个元素
"""
return islice(it, n, None)
# 获取序列的头
head = next
# 获取除第一个元素外的尾
tail = partial(drop, 1)
此外,很常见的另一个函数是获得一个递推的迭代器函数,即已知x, f,获得(x, f(x), f(f(x)),...)
def iterate(f, x):
yield x
yield from iterate(f, f(x))
注意,要防止mutation,就是说到底复制的是指针还是指针代表的数,显然下面的写法是有问题的:
def iterate(f, x):
while True:
yield x
x = f(x)
一个简单的避免方法是:
def iterate(f, x):
return accumulate(repeat(x), lambda fx, _:f(fx))
使iterate
def lazy_integers():
return iterate(add1, 0)
take(10, lazy_integers())
一个例子:斐波那契数列
基本写法
def fib(n):
if n == 0: return 1
if n == 1: return 1
return fib(n - 1) + fib(n - 2)
[fib(i) for i in range(10)]
升级写法——mutable but functional
def fibs():
a, b = 0, 1
while True:
yield b
a, b = b, a + b
take(10, fibs())
Haskell-Like 写法
def fibs():
yield 1
yield 1
yield from map(add, fibs(), tail(fibs()))
take(10, fibs())
尾递归的haskell-like版本
def fibs():
yield 1
yield 1
fibs1, fibs2 = tee(fibs())
yield from map(add, fibs1, tail(fibs2))