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))
相關文章
相關標籤/搜索