python函數,專欄總目錄:
函數式編程的一個特點就是,允許把函數本身作爲參數傳入另一個函數,還允許返回一個函數!
1、傳入函數
既然變量可以指向函數,函數的參數能接收變量,那麼一個函數就可以接收另一個函數作爲參數,這種函數就稱之爲高階函數。一個最簡單的高階函數:
def add(x, y, f):
return f(x) + f(y)
當我們調用add(-5, 6, abs)時,參數x,y和f分別接收-5,6和abs,根據函數定義,我們可以推導計算過程爲:
x = -5
y = 6
f = abs
f(x) + f(y) ==> abs(-5) + abs(6) ==> 11
return 11
2、高階函數
2.1、map
map()函數接收兩個參數,一個是函數,一個是Iterable,map將傳入的函數依次作用到序列的每個元素,並把結果作爲新的Iterator返回。
>>> def f(x):
... return x * x
>>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> list(r)
[1, 4, 9, 16, 25, 36, 49, 64, 81]
map()傳入的第一個參數是f,即函數對象本身。由於結果r是一個Iterator,Iterator是惰性序列,因此通過list()函數讓它把整個序列都計算出來並返回一個list。
2.2、reduce
reduce()把一個函數作用在一個序列[x1, x2, x3, ...]上,這個函數必須接收兩個參數,reduce把結果繼續和序列的下一個元素做累積計算,其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
實例1、一個序列求和:
>>> from functools import reduce
>>> def fn(x, y):
... return x * 10 + y
>>> reduce(fn, [1, 3, 5, 7, 9])
13579
實例2、把str轉換爲int:
>>> from functools import reduce
>>> def fn(x, y):
... return x * 10 + y
>>> def char2num(s):
... digits = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
... return digits[s]
>>> reduce(fn, map(char2num, '13579'))
13579
2.3、filter
filter()函數用於過濾序列。filter()接收一個函數和一個序列。filter()把傳入的函數依次作用於每個元素,然後根據返回值是True還是False決定保留還是丟棄該元素。
實例1、刪除序列空字符串:
把一個序列中的空字符串刪掉,可以這麼寫:
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。
2.4、sorted
注:sorted高階函數不改變原list
1)Python內置的sorted()函數就可以對list進行從小到大排序,reverse=True時從大道小排序:
>>> sorted([36, 5, -12, 9, -21], reverse=False)
[-21, -12, 5, 9, 36]
2)高階函數,自定義排序規則
sorted()函數也是一個高階函數,它還可以接收一個key函數來實現自定義的排序,例如按絕對值大小排序:
>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]
key指定的函數將作用於list的每一個元素上,並根據key函數返回的結果進行排序。
忽略大小寫字符串排序的例子:
這樣,我們給sorted傳入key函數,即可實現忽略大小寫的排序:
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
['about', 'bob', 'Credit', 'Zoo']
3)按object中關鍵字排序
list_info = [{"a": 1, "b": 3}, {"a": 3, "b": 1}, {"a": 2, "b": 2}]
ret = sorted(list_info, key=lambda x: x['b'])
print("ret:", ret)
3、返回函數/閉包
3.1、函數作爲返回值
高階函數除了可以接受函數作爲參數外,還可以把函數作爲結果值返回。
實例1、實現一個可變參數的求和:
不返回求和的結果,而是返回求和的函數:
def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum
>>> f = lazy_sum(1, 3, 5, 7, 9)
>>> f()
25
lazy_sum是一個閉包,當我們調用lazy_sum()時,每次調用都會返回一個新的函數。
3.2、閉包
返回的函數在其定義內部引用了局部變量args,當一個函數返回了一個函數後,其內部的局部變量還被新函數引用。
實例1、測試返回的函數並沒有立刻執行,直到調用了f()才執行:
def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 = count()
=> 9, 9, 9
在上面的例子中,每次循環,都創建了一個新的函數,然後,把創建的3個函數都返回了。你可能認爲調用f1(),f2()和f3()結果應該是1,4,9,但實際結果是:
全部都是9!原因就在於返回的函數引用了變量i,但它並非立刻執行。等到3個函數都返回時,它們所引用的變量i已經變成了3,因此最終結果爲9。
注:返回閉包時牢記,返回函數不要引用任何循環變量,或者後續會發生變化的變量。
實例2、閉包中引用循環變量:
方法是再創建一個函數,用該函數的參數綁定循環變量當前的值,無論該循環變量後續如何更改,已綁定到函數參數的值不變:
def count():
def f(j):
def g():
return j*j
return g
fs = []
for i in range(1, 4):
fs.append(f(i)) # f(i)立刻被執行,因此i的當前值被傳入f()
return fs
再看看結果:
>>> f1, f2, f3 = count() >>> f1() 1 >>> f2() 4
缺點是代碼較長,可利用lambda函數縮短代碼。
4、匿名函數
python中匿名函數通過lambda關鍵字實現。
>>> f1, f2, f3 = count()
>>> f1()
1
>>> f2()
4
冒號前面的x表示函數參數,匿名函數有個限制,就是隻能有一個表達式,不用寫return,返回值就是該表達式的結果。
5、裝飾器
5.1、無參數的函數裝飾器
在函數調用前後自動打印日誌,但又不希望修改now()函數的定義,這種在代碼運行期間動態增加功能的方式,稱之爲“裝飾器”(Decorator)。
本質上,decorator就是一個返回函數的高階函數,接受一個函數作爲參數,並返回一個函數。所以,我們要定義一個能打印日誌的decorator,可以定義如下:
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
觀察上面的log,因爲它是一個decorator,藉助Python的@語法,把decorator置於函數的定義處:
@log
def now():
print('2015-3-25')
把@log放到now()函數的定義處,相當於執行了語句:now = log(now)
wrapper()函數的參數定義是(*args, **kw),wrapper()函數可以接受任意參數的調用。在wrapper()函數內,首先打印日誌,再緊接着調用原始函數。
5.2、帶參數的函數裝飾器
如果decorator本身需要傳入參數,那就需要編寫一個返回decorator的高階函數,寫出來會更復雜。比如,要自定義log的文本:
def log(text):
def decorator(func):
def wrapper(*args, **kw): #wrapper包裝
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
和兩層嵌套的decorator相比,3層嵌套的效果是這樣的:
>>> now = log('execute')(now)
上面的語句,首先執行log('execute'),返回的是decorator函數,再調用返回的函數,參數是now函數,返回值最終是wrapper函數。
5.3、完整的decorator的寫法,解決__name__問題
以上兩種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 wrapper
或者針對帶參數的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 decorator
import functools是導入functools模塊。模塊的概念稍候講解。現在,只需記住在定義wrapper()的前面加上@functools.wraps(func)即可。
6、偏函數
6.1、函數作爲返回值
Python的functools模塊提供了很多有用的功能,其中一個就是偏函數(Partial function)。
int()函數還提供額外的base參數,默認值爲10。如果傳入base參數,就可以做N進制的轉換:
>>> int('12345', base=8)
5349
>>> int('12345', 16)
74565
假設要轉換大量的二進制字符串,每次都傳入int(x, base=2)非常麻煩,於是,我們想到,可以定義一個int2()的函數,默認把base=2傳進去:
def int2(x, base=2):
return int(x, base)
這樣,我們轉換二進制就非常方便了:
>>> int2('1000000')
64
>>> int2('1010101')
85
functools.partial就是幫助我們創建一個偏函數的,不需要我們自己定義int2(),可以直接使用下面的代碼創建一個新的函數int2:
>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85
所以,簡單總結functools.partial的作用就是,把一個函數的某些參數給固定住(也就是設置默認值),返回一個新的函數,調用這個新函數會更簡單。
注意到上面的新的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)
當傳入:
max2 = functools.partial(max, 10)
實際上會把10作爲*args的一部分自動加到左邊,也就是:
max2(5, 6, 7)
相當於:
args = (10, 5, 6, 7)
max(*args)
結果爲10。