Python學習筆記——函數式編程

函數式編程就是一種抽象程度很高的編程範式,純粹的函數式編程語言編寫的函數沒有變量,因此,任意一個函數,只要輸入是確定的,輸出就是確定的,這種純函數我們稱之爲沒有副作用。而允許使用變量的程序設計語言,由於函數內部的變量狀態不確定,同樣的輸入,可能得到不同的輸出,因此,這種函數是有副作用的。
函數式編程的一個特點就是,允許把函數本身作爲參數傳入另一個函數,還允許返回一個函數!

Python對函數式編程提供部分支持。由於Python允許使用變量,因此,Python不是純函數式編程語言。

高階函數

1、變量可以指向函數

2、函數名也是變量

3、傳入函數

既然變量可以指向函數,函數的參數能接收變量,那麼一個函數就可以接收另一個函數作爲參數,這種函數就稱之爲高階函數。函數式編程就是指這種高度抽象的編程範式。

4、map函數

map()函數接收兩個參數,一個是函數,一個是Iterable,map將傳入的函數依次作用到序列的每個元素,並把結果作爲新的Iterator返回。

5、reduce函數

reduce()把一個函數作用在一個序列[x1, x2, x3, …]上,這個函數必須接收兩個參數,reduce把結果繼續和序列的下一個元素做累積計算,其效果就是:

reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

利用map和reduce編寫一個str2float函數,把字符串’123.456’轉換成浮點數123.456:

# -*- coding: utf-8 -*-
from functools import reduce

def str2float(s):

    DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
    def char2num(s):
        return DIGITS[s]
    def fn1(x,y):
        return x*10+y
    def fn2(x,y):
        return x*0.1+y
    return reduce(fn1,map(char2num,s[:s.find('.')]))+reduce(fn2,map(char2num,s[-1:s.find('.'):-1]+'0'))
 
print('str2float(\'123.456\') =', str2float('123.456'))
if abs(str2float('123.456') - 123.456) < 0.00001:
    print('測試成功!')
else:
    print('測試失敗!')

6、filter函數

filter()函數用於過濾序列。filter()把傳入的函數依次作用於每個元素,然後根據返回值是True還是False決定保留還是丟棄該元素。

把一個序列中的空字符串刪掉,可以這麼寫:

def not_empty(s):
    return s and s.strip()

list(filter(not_empty, ['A', '', 'B', None, 'C', '  ']))
# 結果: ['A', 'B', 'C']

注意到filter()函數返回的是一個Iterator,也就是一個惰性序列,所以要強迫filter()完成計算結果,需要用list()函數獲得所有結果並返回list。

回數是指從左向右讀和從右向左讀都是一樣的數,例如12321,909。請利用filter()篩選出回數:

# -*- coding: utf-8 -*-
def is_palindrome(n):
    str_n = str(n)
    length_n = len(str_n)
    flag = True
    for i in range(length_n//2)  :
        if str_n[i] != str_n[length_n-1-i]:
            flag = False
    return flag

7、sorted函數

Python內置的sorted()函數就可以對list進行排序。,此外也是一個高階函數,它還可以接收一個key函數來實現自定義的排序,例如按絕對值大小排序:

>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]

默認情況下,對字符串排序,是按照ASCII的大小比較的。

要進行反向排序,不必改動key函數,可以傳入第三個參數reverse=True:

>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
['Zoo', 'Credit', 'bob', 'about']

用sorted()排序的關鍵在於實現一個映射函數。

L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
#用sorted()對上述列表分別按名字排序:
def by_name(t):
    return str.lower(t[0])
#再按成績從高到低排序:
def by_score(t):
    return -t[1]
L2 = sorted(L, key=by_name)
print(L2)

返回函數

高階函數除了可以接受函數作爲參數外,還可以把函數作爲結果值返回。

def lazy_sum(*args):
    def sum():
        ax = 0
        for n in args:
            ax = ax + n
        return ax
    return sum

在這個例子中,我們在函數lazy_sum中又定義了函數sum,並且,內部函數sum可以引用外部函數lazy_sum的參數和局部變量,當lazy_sum返回函數sum時,相關參數和變量都保存在返回的函數中,這種稱爲“閉包(Closure)”的程序結構擁有極大的威力。

閉包

返回閉包時牢記一點:返回函數不要引用任何循環變量,或者後續會發生變化的變量。
另一個需要注意的問題是,返回的函數並沒有立刻執行,而是直到調用了f()才執行。我們來看一個例子:

def count():
    fs = []
    for i in range(1, 4):
        def f():
             return i*i
        fs.append(f)
    return fs

f1, f2, f3 = count()

在上面的例子中,每次循環,都創建了一個新的函數,然後,把創建的3個函數都返回了。

你可能認爲調用f1(),f2()和f3()結果應該是1,4,9,但實際結果是:

>>> f1()
9
>>> f2()
9
>>> f3()
9

全部都是9!原因就在於返回的函數引用了變量i,但它並非立刻執行。等到3個函數都返回時,它們所引用的變量i已經變成了3,因此最終結果爲9。

def createCounter():
    L=[0]
    def counter():
        L[0]=(L[0]+1)
        return L[0]
    return counter

def createCounter():
    a = 0
    def counter():
        nonlocal a
        a += 1
        return a
    return counter

閉包中內部函數要修改外部函數局部變量L,只有兩個辦法:
1、把L變成一個容器,或者說把L變成可變對象,然後內部函數就可以修改了。
這裏拓展一下:我們目前最熟悉的容器/可變對象就是list了;如果是單純的n,它作爲一個整體仍然是不可變的。這點可以回憶一下前面的內容,爲什麼tuple是不可變對象,但tuple裏面的list元素的內容仍然可以改變?
2、第二個方法,就是在內部函數裏給外部變量加一個nonlocal聲明,指示內部函數去其他領域獲取這個變量。

匿名函數

在Python中,對匿名函數提供了有限支持。

>>> list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
[1, 4, 9, 16, 25, 36, 49, 64, 81]

關鍵字lambda表示匿名函數,冒號前面的x表示函數參數。

匿名函數有個限制,就是只能有一個表達式,不用寫return,返回值就是該表達式的結果。
用匿名函數有個好處,因爲函數沒有名字,不必擔心函數名衝突。此外,匿名函數也是一個函數對象,也可以把匿名函數賦值給一個變量,再利用變量來調用該函數。同樣,也可以把匿名函數作爲返回值返回。

裝飾器

>>> def now():
...     print('2015-3-25')
...
>>> f = now
>>> f()
2015-3-25

假設我們要增強now()函數的功能,比如,在函數調用前後自動打印日誌,但又不希望修改now()函數的定義,這種在代碼運行期間動態增加功能的方式,稱之爲“裝飾器”(Decorator)。

如果decorator本身需要傳入參數,那就需要編寫一個返回decorator的高階函數,寫出來會更復雜。比如,要自定義log的文本:

import functools

def log(text):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator

這個3層嵌套的decorator用法如下:

@log('execute')
def now():
    print('2015-3-25')

執行結果如下:

>>> now()
execute now():
2015-3-25

我們來剖析上面的語句,首先執行log(‘execute’),返回的是decorator函數,再調用返回的函數,參數是now函數,返回值最終是wrapper函數。

Python內置的functools.wraps把原始函數的__name__等屬性複製到wrapper()函數中。

偏函數

當函數的參數個數太多,需要簡化時,使用functools.partial可以創建一個新的函數,這個新函數可以固定住原函數的部分參數,從而在調用時更簡單。

>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85

注意到上面的新的int2函數,僅僅是把base參數重新設定默認值爲2,但也可以在函數調用時傳入其他值:

>>> int2('1000000', base=10)
1000000

最後,創建偏函數時,實際上可以接收函數對象、*args**kw這3個參數,當傳入:

int2 = functools.partial(int, base=2)

實際上固定了int()函數的關鍵字參數base,也就是:

int2('10010')

相當於:

kw = { 'base': 2 }
int('10010', **kw)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章