目錄python
在Python中一切皆對象,固然也包括函數。函數在Python中是一等公民
(First Class Object)。即函數與字符串數組整型無異,它能夠被命名能夠被賦值能夠看成參數被傳進另外一個函數也能夠被另外一個函數看成返回值能夠放在任何位置,簡單來講:程序員
那什麼是高階函數呢?在Python中咱們能夠理解爲:當一個函數接受另外一個函數做爲參數使用,或者一個函數最後返回了另一個函數。在這兩種狀況下,這個函數就能夠稱之爲高階函數(知足以上任意一種狀況便可,沒必要同時知足)。算法
數學概念: y = g(f(x))數組
def outer(x): def inner(step=1): nonlocal x # 聲明局部變量x不是一個局部變量,應該在外層尋找 x += step return x return inner foo1 = outer(10) foo2 = outer(10) print(foo1()) print(foo2())
內層函數inner還引用了外層函數的自由變量x,造成了閉包,因爲外部函數返回了內部函數,因此這就是一個典型的高階函數。針對上面實例還須要說明是:閉包
In [1]: def outer(x): ...: def inner(step=1): ...: nonlocal x # 聲明局部變量x不是一個局部變量,應該在外層尋找 ...: x += step ...: return x ...: return inner ...: foo1 = outer(10) ...: foo2 = outer(10) ...: foo1 is foo2
is
的比較作則是:先比較元素的內存地址,而後比較元素的內容。首先 foo1和foo2 屬於不一樣的對象,因此內存地址確定不一樣,而後因爲函數對象沒有實現函數內容的比較因此這裏返回Falseapp
從頭構建一個相似於內置函數sorted的函數,來體會高階函數的做用,順便理解一下sorted的原理。dom
def sort(iterable, *, key=None, reverse=False): new_list = [] for value in iterable: for i, k in enumerate(new_list): if value < k: new_list.insert(i, value) break else: new_list.append(value) return new_list print(sort([1,6,2,7,9,3,5]))
分析:函數
def sort(iterable, *, key=None, reverse=False): new_list = [] for value in iterable: for i, k in enumerate(new_list): flag = value > k if reverse else value < k # if reverse: # flag = value > k # else: # flag = value < k if flag: new_list.insert(i, value) break else: new_list.append(value) return new_list # return new_list[::-1] print(sort([1,6,2,7,9,3,3,5]))
分析:學習
def sort(iterable, *, key=None, reverse=False): new_list = [] for value in iterable: value_new = key(value) if key else value for i, k in enumerate(new_list): k_new = key(k) if key else k flag = value_new > k_new if reverse else value_new < k_new if flag: new_list.insert(i, value) break else: new_list.append(value) return new_list # return new_list[::-1] print(sort(['a',1,2,'b'], key=str, reverse=True))
分析:優化
def sort(iterable, *, key=None, reverse=False): new_list = [] for value in iterable: value_new = key(value) if callable(key) else value for i, k in enumerate(new_list): k_new = key(k) if callable(key) else k flag = value_new > k_new if reverse else value_new < k_new if flag: new_list.insert(i, value) break else: new_list.append(value) return new_list print(sort(['a',1,2,'b'], key=lambda x:str(x), reverse=True))
Python內置了不少高階函數的應用,這裏僅介紹較爲經常使用的。
sorted(iterable, /, *, key=None, reverse=False)
當即返回一個新的列表,對一個可迭代對象的全部元素排序。
key
: 排序規則爲key定義的函數reverse
: 表示是否進行翻轉排序In [49]: lst Out[49]: [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95] In [50]: import random In [51]: random.shuffle(lst) In [52]: lst Out[52]: [70, 45, 90, 40, 30, 80, 25, 55, 5, 75, 85, 95, 50, 20, 35, 15, 10, 60, 65, 0] In [53]: sorted(lst,reverse=True) Out[53]: [95, 90, 85, 80, 75, 70, 65, 60, 55, 50, 45, 40, 35, 30, 25, 20, 15, 10, 5, 0] In [54]: lst.append('a') In [55]: sorted(lst,key=str,reverse=True) Out[55] :['a', 95, 90, 85, 80, 75, 70, 65, 60, 55, 50, 5, 45, 40, 35, 30, 25, 20, 15, 10, 0] In [58]: sorted(lst,key=lambda x:str(x)) Out[58]: [0, 10, 15, 20, 25, 30, 35, 40, 45, 5, 50, 55, 60, 65, 70 75, 80, 85, 90, 95, 'a'] In [59]: lst.remove('a') In [61]: sorted(lst,key=lambda x : 100 - x ) Out[61]: [95, 90, 85, 80, 75, 70, 65, 60, 55, 50, 45, 40, 35, 30, 25, 20, 15, 10, 5, 0]
這是匿名函數最經常使用的場景之一
)功能等同於直接調用str函數
sorted直接返回一個新的列表,而列表也有sort方法,經過
list.sort()
進行排序是直接原地進行排序的。
filter(function or None, iterable) --> filter object
過濾可迭代對象的元素,返回一個迭代器
function
:表示一個函數,它的功能是:每次從可迭代對象iterable取出一個元素交給function函數處理,若是返回True,則保留該元素,不然提出該元素,若是是None,表示剔除等效False的對象iterable
:可迭代對象In [62]: lst1 = [1,9,55,150,-3,78,28,123] In [64]: list(filter(lambda x:x%3==0,lst1)) Out[64]: [9, 150, -3, 78, 123] # 等同於 In [66]: def func(iterable): ...: for i in iterable: ...: if i % 3 == 0: ...: yield i ...: In [67]: list(func(lst1)) Out[67]: [9, 150, -3, 78, 123] # 或者 In [66]: def func(iterable): ...: for i in iterable: ...: if (lambda x:x%3==0)(i): ## l這裏屬於函數調用:func()() ...: yield i ...:
map(func, *iterables) --> map object
對多個可迭代對象的元素按照指定的函數進行映射,返回一個迭代器
func
:一個函數,用於處理iterable的元素,在指定多個iterable對象是,函數的參數數量與iterable的數量是相等的。*iterable
:一個或多個可迭代對象In [69]: list(map(str,range(10))) Out[69]: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] In [70]: list(map(lambda x:x+1,range(10))) Out[70]: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] In [73]: dict(list(map(lambda x,y:(x,y),'abcde',range(10)))) Out[73]: {'a': 0, 'b': 1, 'c': 2, 'd': 3, 'e': 4} In [74]: dict(map(lambda x:(x%5,x),range(500))) Out[74]: {0: 495, 1: 496, 2: 497, 3: 498, 4: 499}
map對象的長度,等同於最小的iterable長度。(木桶效應)
在計算機科學中,柯里化(英語:Currying),又譯爲卡瑞化或加里化,是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,而且返回接受餘下的參數並且返回結果的新函數的技術。-- 來自維基百科的解釋
總結一下:柯里化指的就是將原來接受兩個參數的函數變成新的接受一個參數的函數的過程。新的函數返回一個以原有第二個參數爲參數的函數。其數學表達式爲:
z = f(x, y) 轉換成 z = f(x)(y) 的形式
這裏把經典的add函數拿來進行轉換:
def add(x, y): return x + y def new_add(x): def inner(y): return x + y return inner print(add(4, 5)) # 9 print(new_add(4)(5)) # 9 print(add(4, 5) == new_add(4)(5)) # True
經過函數嵌套,就能夠完成函數的柯里化了,對柯里化有所瞭解之後,那麼咱們就能夠繼續來看Python中對咱們小白來講的第一個難點:裝飾器
什麼是裝飾器?歸納的講,裝飾器的做用就是爲已經存在的對象添加額外的功能。咱們接下來從一個需求開始學習裝飾器。
如今有以下函數,咱們須要將這個函數的日誌打印到終端
def add(x,y): print('我被調用執行啦') # 新增打印日誌語句 return x + y add(100,200)
可是仔細思考,打印日誌是一個獨立的功能,它和add函數自己並無什麼關聯關係,咱們說一個函數是爲完成一個工程的,因此直接寫在函數裏面不是不能夠,可是不建議,而且打印日誌屬於調試信息功能,與業務無關,不該該放在業務函數加法中。
若是不須要寫在函數的裏面,那麼咱們得想辦法寫在函數的外面
def add(x, y): return x + y def logger(fn, x, y): print('函數開始執行') res = fn(x, y) print('函數執行完畢') return res logger(add,4,5)
def add(x, y): return x + y def logger(fn, *args, **kwargs): print('函數開始執行') res = fn(*args, **kwargs) print('函數執行完畢') return res logger(add,4,5)
使用logger(add,4,5)
來調用咱們的add函數真是太醜了,給其餘人看,可能人家也不知道你要幹啥,結合前面所學的柯里化,咱們進行以下變更
def add(x, y): return x + y def logger(fn): def wrapper(*args, **kwargs): print('函數被執行了') res = fn(*args, **kwargs) print('函數執行完畢') return res return wrapper logger(add)(4,5)
這樣看起來是否是就好看多了?當指定logger(add)(4,5)時,纔會打印日誌,但若是想要在全部調用add函數的地方,咱們還須要在全部調用add的地方修改成logger(add)(參數),想想,若是我能把logger(add)變成add是否是就能夠直接寫成add(4,5)了呢?
logger(add)(4,5) -------- add = logger(add) add(4,5) # 將add從新指向了新的函數wrapper
按照柯里化的原型中logger(add)返回了一個函數wrapper,而咱們的(4,5)實際上是傳遞給了wrapper,結合咱們前面所學的高階函數,這裏的wrapper,是一個閉包函數,由於在內部對fn進行了執行,並且增長了打印日誌的功能,咱們在執行wrapper的同時,也會執行原來的函數fn,而且添加了打印日誌的功能,因此logger就是一個裝飾器函數
!!!
語法糖(Syntactic sugar),也譯爲糖衣語法,是由英國計算機科學家彼得·蘭丁發明的一個術語,指計算機語言中添加的某種語法,這種語法對語言的功能沒有影響,可是更方便程序員使用。 語法糖讓程序更加簡潔,有更高的可讀性。Python針對咱們剛剛編寫的logger(add)函數,進行了語法糖優化,因此下面是咱們使用語法糖以後的
def logger(fn): def wrapper(*args, **kwargs): print('函數被執行了') res = fn(*args, **kwargs) print('函數執行完畢') return res return wrapper @logger # 等於 add = logger(add) def add(x, y): return x + y add(4,5)
當解釋器執行到
@logger
時,會自動把它下面的函數看成參數,傳給logger函數,因此這裏@logger
其實就等於add = logger(add)
另外,logger必需要定義在add函數以前才能夠被裝載!這一點很重要!
利用裝飾器計算以下函數的運行時間
import time import datetime def logger(fn): def wrapper(*args, **kwargs): start = datetime.datetime.now() res = fn(*args, **kwargs) total_seconds = (datetime.datetime.now() - start).total_seconds() print('函數:{} 執行用時:{}'.format(wrapper.__name__,total_seconds)) return res return wrapper @logger def add(x, y): time.sleep(2) return x + y 執行結果: In [76]: add(4,5) 函數:wrapper 執行用時:2.000944
這裏
__name__
表示函數的名稱.
什麼鬼?這裏爲何打印的是wrapper啊,爲何不是add呢?這樣的話,別人不就發現我把這個函數給偷偷換掉了嗎?不行不行,我得想個辦法把函數的屬性複製過來,因爲這個功能和打印用時的裝飾器不是一個功能,那麼咱們還得給裝飾器另加一個裝飾器。-_-!
import time import datetime def copy_properties(old_fn): def wrapper(new_fn): new_fn.__name__ = old_fn.__name__ return new_fn return wrapper def logger(fn): @copy_properties(fn) # wrapper = copy_properties(fn)(wrapper) def wrapper(*args, **kwargs): start = datetime.datetime.now() res = fn(*args, **kwargs) total_seconds = (datetime.datetime.now() - start).total_seconds() print('函數:{} 執行用時:{}'.format(wrapper.__name__,total_seconds)) return res return wrapper @logger def add(x, y): time.sleep(2) return x + y add(4,5)
@copy_properties(fn)
時,會把下面的wraper裝入,等於wrapper = copy_properties(fn)(wrapper)
Python的內置模塊functools中,內置了不少經常使用的高階函數,其中wraps就是用來拷貝函數的屬性及簽名信息的。利用wraps,咱們就不須要本身編寫copy_properties函數了,下面是修改後的版本
import time import datetime import functools def logger(fn): @functools.wraps(fn) # wrapper = functools.wraps(fn)(wrapper) def wrapper(*args, **kwargs): start = datetime.datetime.now() res = fn(*args, **kwargs) total_seconds = (datetime.datetime.now() - start).total_seconds() print('函數:{} 執行用時:{}'.format(wrapper.__name__,total_seconds)) return res return wrapper @logger def add(x, y): time.sleep(2) return x + y add(4,5)
經過使用 @functools.wraps(fn)
咱們能夠方便的拷貝函數的屬性簽名信息,好比:'module', 'name', 'qualname', 'doc','annotations'等,這些屬性信息,將在後續部分進行講解,這裏知道便可
上面章節講到的是帶一個參數(函數)的裝飾器,在Python中這種裝飾器被稱爲無參裝飾器,由於語法糖的表現形式就是 @logger
,下面要說的是代參數的裝飾器,即@logger(50)
以上述函數爲例,咱們須要記錄當函數執行超過必定時間時的日誌信息,該怎麼辦呢?假設這個時間是5秒,那麼很顯然,咱們須要把這個時間變量傳入到裝飾器中進行判斷。也就是說咱們須要寫成這種形式:
logger(5)(add)
looger(5)返回的是一個函數,不然沒法將add傳入
import time import datetime import functools def logger(var): def inner(func): def wrapper(*args, **kwargs): start = datetime.datetime.now() res = func(*args, **kwargs) total_seconds = (datetime.datetime.now() - start).total_seconds() if total_seconds > var: print('函數執行時間過長') return res return wrapper return inner @logger(5) # logger(5)(add) def add(x, y): time.sleep(6) return x + y
是否是很簡單? 在掌握了柯里化以及無參裝飾器。
代參裝飾器有以下特色:
能夠看作在裝飾器外層又加了一層函數
裝飾器何時被執行?,還記得@logger等於什麼嗎? add = logger(add) 等號等於賦值,是否是要先計算右邊的?因此,裝飾器在函數定義階段就已經被執行了!不是等到被裝飾的函數執行時,才執行哦!真正執行時,每次都會生成一個新的wrapper被調用而已!