天天看點

④Python函數式程式設計

④Python函數式程式設計

④Python函數式程式設計

  • ​​高階函數​​
  • ​​map​​
  • ​​reduce​​
  • ​​map與reduce結合使用​​
  • ​​filter​​
  • ​​sorted​​
  • ​​傳回函數​​
  • ​​匿名函數​​
  • ​​裝飾器​​
  • ​​偏函數​​

函數式程式設計的一個特點是,允許把函數本身作為參數傳入另一個函數,還允許傳回一個函數!

高階函數

高階函數有如下幾個特征,逐漸深入了解。

  • 變量可以指向函數
>>> abs(-10)
 10
 >>> abs
 <built-function abs>
 >>> x=abs(-10)
 >>> x
 10
 >>> f = abs
 >>> f(-10)
 10      

如果把函數本身指派給變量,即便相知相函數,那麼可以通過變量來調用函數。

  • 函數名也是變量

    這裡僅僅說明,函數名可以當作變量使用,但是實際程式設計過程中,基本不會将函數名當成變量來使用。

>>> abs=10
 >>> abs
 10      
  • 傳入函數

    變量可以指向函數,函數的參數可以接受變量,那麼自然而然一個函數也能接受另一個函數作為參數,這種函數就稱之為高階函數。

def add(x,y,f):
    return f(x)+f(y)      

上面定義的函數可以通過​

​add(-5,6,abs)​

​這種方式調用。

map

接收兩個參數,一個是函數,一個是​

​Iterable​

​​,​

​map​

​​将傳入的函數依次作用到序列的每個元素,并把結果作為新的​

​Iterator​

​傳回。

舉例說明:

>>> def square_func(x):
        return x*x

>>> r=map(square_func,[1,2,3,4,5])
>>> list(r)
[1,4,9,16,25]      

​map​

​​傳回的是惰性序列,是以通過​

​list()​

​函數把整個序列都計算出來并傳回一個list。

實際上​

​map()​

​作為高階函數,将運算規則抽象了,是以,我們不但可以計算簡單的求平方的函數,還可以計算任意複雜的函數。例如将一個list所有數字都轉化為字元串。

>>> list(map(str,[1,2,3,4,5,6,7,8,9]))
['1', '2', '3', '4', '5', '6', '7', '8', '9']      

reduce

​reduce​

​​把一個函數作用在一個序列上,這個函數必須接受兩個參數,​

​reduce​

​将結果繼續和序列的下一個元素做累計計算,其效果就是:

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

例如對一個序列進行求和就可以使用​

​reduce​

​實作:

from functools import reduce
def add(x,y):
    return x+y

r = reduce(add,[1,2,3,4])
print(r)#result:10      

map與reduce結合使用

map可以與reduce實作更多的功能,甚至可以和​

​lambda​

​表達式結合使用。

from functools import reduce

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 str2int(s):
    return reduce(lambda x, y: x * 10 + y, map(char2num, s))      

上面的代碼就可以實作将字元串轉化為整數的功能。

filter

Python内建的​

​filter()​

​函數用于過濾序列。

和​

​map()​

​​函數類似,​

​filter()​

​​也接受一個函數和一個序列,但是和​

​map()​

​​不同的是,​

​filter()​

​​把傳入的函數依次作用于每個元素,然後根據傳回值是​

​True​

​​和​

​False​

​決定保留還是丢棄該元素。

def is_odd(n)
    return n % 2 ==1      

sorted

排序是程式中經常會使用的功能,無論使用冒泡排序還是快速排序,排序的核心是比較兩個元素的大小。如果是數字,可以直接比較,但是如果是字元串和字典,直接比較數學上的大小是沒有意義的,此時必須通過函數抽象出來。

大家所熟知的​

​sorted()​

​​函數其實也是個高階函數,可以接受一個​

​key​

​函數來實作自定義的排序。

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

key指定的函數會作用在list的每個元素上,并根據key函數傳回的結果進行排序。

高階函數的抽象能力非常強,而且,核心代碼可以保持的非常簡潔。

傳回函數

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

可以通過傳回凡是來定義一個求和函數:

def lazy_sum(*args):
    def sum():
        ax=0
        for n in args:
            ax = ax + n
        return ax
    return sum
>>> f =lazy_sum(*args)
>>> f
<function lazy_sum.<locals>.sum at 0x101c6ed90>
>>> f()#調用函數,産生結果      

當​

​lazy_sum​

​​函數傳回函數​

​sum​

​時,相關參數和變量都儲存在傳回的函數中,這種稱為“閉包”(Closure)的程式結構擁有極大的威力。

​lazy_sum​

​函數的每次調用都會傳回一個新的函數,即使傳入相同的參數。

  • 閉包

    傳回閉包時注意一點:傳回函數不要引用任何循環變量,或者後續會發生變化的變量。

    比如下面這種情況就是錯誤的:

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

本以為會傳回1,4,9但是實際上會傳回9,9,9。這是因為等到3個函數都傳回時,他們所引用的變量​

​i​

​​已經變成​

​3​

​​,是以最終結果為​

​9​

​。

為了解決這個問題,可以再建立一個函數,用該函數的參數綁定循環變量目前的值。

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

匿名函數

也就是常說的​

​lambda​

​表達式,一定程度上,匿名函數可以簡化代碼的書寫,但是也會增加代碼的了解難度。

list(map(lambda x:x*x,[1,2,3,4,5,6,7,8,9]))      

​lambda​

​表示匿名函數。

匿名函數沒有函數名,不需要擔心函數沖突,同時匿名函數是一個函數對象,可以把匿名函數指派給一個變量,再利用變量來調用該函數。

裝飾器

由于函數也是一個對象,而且函數對象可以被賦給變量,是以,通過變量也能調用該函數。

def now():
    print('5/14/2020')
>>> f=now
>>> f()      

函數對象有一個​

​__name__​

​屬性,可以拿到函數名字。

>>> now.__name__
now      

假設現在,要增強​

​now()​

​​函數的功能,比如,在函數調用前後自動列印日志,但又不希望修改​

​now()​

​函數定義,這種在代碼運作期間動态增加功能的方法,稱之為“裝飾器”。

def log(func):
    def wrapper(*args,**kw):
        print('call %s()'%func.__name__)
        return func(*args,**kw)
    return      

上面的​

​log​

​,因為是一個decorator,是以接受一個函數作為參數,并傳回一個函數。借助Python的@羽凡,把decorator置于函數的定義處:

@log
def now():
    print('5/14/2020')
>>> now()
call now():
5/14/2020      

把​

​@log​

​​放到​

​now()​

​函數的定義處,相當于執行了語句:

now=log(now)      

由于​

​log()​

​​是一個decorator,傳回一個函數,是以,原來的​

​now()​

​​函數仍然存在,隻是現在同名的​

​now​

​​變量指向了新的函數,于是調用​

​now()​

​​将執行新函數,即在​

​log()​

​​函數中傳回的​

​wrapper()​

​函數。

​wrapper()​

​​函數的參數定義是​

​(*args, **kw)​

​​,是以,​

​wrapper()​

​​函數可以接受任意參數的調用。在​

​wrapper()​

​函數内,首先列印日志,再緊接着調用原始函數。

如果decorator本身需要傳入參數,那就需要編寫一個傳回decorator的高階函數,寫出來會更加複雜。

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

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

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

執行結果如下:

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

和兩層嵌套的decorator相比,3層嵌套的效果是這樣的:

>>> now = log('execute')(now)      

我們來剖析上面的語句,首先執行​

​log('execute')​

​​,傳回的是​

​decorator​

​​函數,再調用傳回的函數,參數是​

​now​

​​函數,傳回值最終是​

​wrapper​

​函數。

以上兩種decorator的定義都沒有問題,但還差最後一步。因為我們講了函數也是對象,它有​

​__name__​

​​等屬性,但你去看經過decorator裝飾之後的函數,它們的​

​__name__​

​​已經從原來的​

​'now'​

​​變成了​

​'wrapper'​

​:

>>> now.__name__
'wrapper'      

因為傳回的那個​

​wrapper()​

​​函數名字就是​

​'wrapper'​

​​,是以,需要把原始函數的​

​__name__​

​​等屬性複制到​

​wrapper()​

​函數中,否則,有些依賴函數簽名的代碼執行就會出錯。

不需要編寫​

​wrapper.__name__ = func.__name__​

​​這樣的代碼,Python内置的​

​functools.wraps​

​就是幹這個事的,是以,一個完整的decorator的寫法如下:

import functools

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

或者針對帶參數的decorator:

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      

偏函數

import functools
int2 = functools.partial(int,base=2)#按照二進制進行轉換,轉換成int數字