Making Your Python Codes More Functional

本篇文章是基于Joel Grus: Learning Data Science Using Functional Python视频的笔记。python

经常使用的函数

currying

在Python中实现科里化的最佳方案是functools.partial。例如如下例子:函数

# 通常版本
def add1(x): return add(1, x)

# FP的版本
add1_functional = partial(add, 1)

reduce、map、filter

这几个是常见的FP中处理列表的函数,在此不作介绍。工具

注意:Python这得reducefunctools包中。指针

iterators(迭代器)

如下是个迭代器的例子:code

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:

迭代器的特色主要包括:视频

  1. 一次只用一个递归

  2. 只有须要时才会产生数据。ip

这两个特色保证了其惰性的特色,而另外一个好处是咱们能够构造无穷序列内存

generator生成器

生成器所要生成的结果其实就是迭代器,以下:ci

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))
相关文章
相关标签/搜索