什么是高階函數?
-- 把函數名當做參數傳給另外一個函數,在另外一個函數中通過參數調用執行
#!/usr/bin/python3 __author__ = 'beimenchuixue' __blog__ = 'http://www.cnblogs.com/2bjiujiu/' def func_x(x): return x * 2 def func_y(y): return y * 3 def func_z(x, y): # 等價於 return func_x(5) + func_y(3) return x(5) + y(3) if __name__ == '__main__': # 把函數當做參數,本質上是把函數的內存地址當做參數傳遞過去, result = func_z(func_x, func_y) print(result)
什么是裝飾器?
-- 在不改變源代碼的基礎上擴展新需求,裝飾器本身也是函數,應用高階函數實現
-- 把被裝飾的函數內存地址當參數傳入裝飾器函數體,通過參數調用被裝飾的函數
裝飾器原則:
-- 不改變源代碼 - 因為函數可能在其他地方各種調用,一改動全身
-- 不改變原函數調用順序 - 源代碼有自己的邏輯處理
-- 裝飾器又叫做語法糖
裝飾器邏輯上格式?
- 高階函數+嵌套函數
需求:
給某個函數增加一個計算運行時間功能
#!/usr/bin/python3 __author__ = 'beimenchuixue' __blog__ = 'http://www.cnblogs.com/2bjiujiu/' import time def total_time(func): # func = hell_word def wrapper(): # 等價於hell_word() start_time =time.time() func() end_time = time.time() print(end_time - start_time) # 打印統計時間 return wrapper # 通過裝飾器給hell_word函數裝上了統計時間的功能,功能邏輯在裝飾器中實現 @total_time def hell_word(): time.sleep(0.5) print('hello word') if __name__ == '__main__': hell_word()
相當於下面的函數邏輯
#!/usr/bin/python3 __author__ = 'beimenchuixue' __blog__ = 'http://www.cnblogs.com/2bjiujiu/' import time
# 裝飾器函數 def total_time(func): def wrapper(): start_time =time.time() func() end_time = time.time() print(end_time - start_time) return wrapper
def hell_word(): time.sleep(0.5) print('hello word') if __name__ == '__main__': # 把函數當做參數傳入裝飾器函數,然后裝飾器函數返回包裹函數wrapper地址,執行裝飾器函數本質上執行包裹函數wrapper中邏輯 total_time(hell_word)()
假如傳入的函數中有參數如何?
-- 需要在wrapper和func中加入收集參數(*args)或收集字典參數(**kwargs),
-- warps和func可自定義名字,默認如此命名
如果原函數有個返回值,該如何?
-- 如果到func結束 直接在func()前面加return
-- 到func未結束,可以func()結果賦值給一個變量,res = func(*args,**kwargs)到新增邏輯結束后加上 return res
需求:
計算出斐波那契數列中第n個數的值?
求一個共有10個台階的樓梯,從下走到上面,一次只能邁出1~3個台階,並且不能后退,有多少中方法?
要求:
通過裝飾器實現剪枝函數
如何邏輯整理這個需求?
斐波那契數列(黃金分割數列),從數列的第3項開始,每一項都等於前兩項之和
每次邁出都是 1~3 個台階,剩下就是 7~9 個台階
如果邁出1個台階,需要求出后面9個台階的走法
如果邁出2個台階,需要求出后面8個台階的走法
如果邁出3個台階,需要求出后面7個台階的走法
此3種方式走法,通過遞歸方式實現,遞歸像樹,每次遞歸都生成子節點函數
#!/usr/bin/python3 __author__ = 'beimenchuixue' __blog__ = 'http://www.cnblogs.com/2bjiujiu/' def jian_zhi(func): # 中間字典,判斷已經是否求解過 median = {} def wrapper(*args): # 假如不在中間字典中,說明沒有求解過,添加到字典中去,在的話,直接返回, 將不在遞歸下去,保證每次遞歸的唯一性 if args not in median: median[args] = func(*args) return median[args] return wrapper @jian_zhi def fibonacci(n): if n <= 1: return 1 return fibonacci(n - 1) + fibonacci(n - 2) @jian_zhi def climb(n, steps): count = 0 # 當最后台階為0的時候,說明最后只是走了一次 if n == 0: count = 1 # 當最后台階不為0的時候,說明還需要走至少一次 elif n > 0: # 對三種情況進行分別處理momo for step in steps: count += climb(n - step, steps) # 返回每次遞歸的計數 return count if __name__ == '__main__': print(climb(10, (1, 2, 3))) print(fibonacci(20))
需求:
實現在裝飾器函數中,保留 被裝飾函數 的元數據
那,什么是函數的元數據?
在函數對象中保存着一些函數的元數據,如:
f.__name__ 函數名
f.__doc__ 函數文檔
f.__moudle__ 函數所屬模塊名
f.__dict__ 屬性字典
f.__defaults__ 默認參數組
……
在使用裝飾器后,在裝飾器里訪問以上屬性時,我們看到的是裝飾器函數的元數據
那,如何解決這個需求?
通過 functools中的wraps或update_wrapper方法實現,其中每個方法都可單獨實現
#!/usr/bin/python3 __author__ = 'beimenchuixue' __blog__ = 'http://www.cnblogs.com/2bjiujiu/' import time from functools import (wraps, update_wrapper, WRAPPER_ASSIGNMENTS, WRAPPER_UPDATES) def count_time(func): """ 給目標函數加上計算運行時間統計 """ # 這個裝上器和update_wrapper一樣,默認參數WRAPPER_ASSIGNMENTS, WRAPPER_UPDATES @wraps(func) def wrapper(*args, **kwargs): start_time = time.time() # 定義result接收函數返回值,並且在裝飾函數最后返回回去 resutl = func(*args, **kwargs) print('運行時間:', time.time() - start_time) return resutl # 其中默認參數 WRAPPER_ASSIGNMENTS, WRAPPER_UPDATES # update_wrapper(wrapper, func) return wrapper @count_time def add(num=100): """ 計算 0~num 累加值,默認num=100 """ time.sleep(1) return sum([x for x in range(num + 1)]) if __name__ == '__main__': print('函數名:', add.__name__) print('屬性字典:', add.__dict__) print('函數默認參數:', add.__defaults__) print('函數所在模塊:', add.__module__) print('函數文檔:', add.__doc__) # 打印兩個默認參數 # WRAPPER_ASSIGNMENTS :__module__', '__name__', '__qualname__', '__doc__', '__annotations__ # WRAPPER_UPDATES:__dict__ print(WRAPPER_ASSIGNMENTS, WRAPPER_UPDATES) result = add() print(result)
需求:
實現一個裝飾器,用它來檢查被裝飾函數的參數類型,裝飾器可以通過函數,指明函數參數類型,進行函數調用的時候,傳入參數,檢測到不匹配時,拋出異常
那,如何解決這個需求?
-- 先要獲取函數的簽名,並且獲得裝飾器中參數,然后把函數簽名和裝飾器中參數對應綁定
-- 把調用函數時候傳入的參數和函數簽名進行綁定
-- 把實參和裝飾器中定義的數據進行類型比較,不匹配拋出異常
#!/usr/bin/python3 __author__ = 'beimenchuixue' __blog__ = 'http://www.cnblogs.com/2bjiujiu/' from inspect import signature def check_type(*ty_args, **ty_kwargs): def out_wrapper(func): # 通過signature方法,獲取函數形參:name, age, height sig = signature(func) # 獲得裝飾器傳來的參數, 函數簽名與之綁定,字典類型 bind_types = sig.bind_partial(*ty_args, **ty_kwargs).arguments print(bind_types) def wrapper(*args, **kwargs): # 給執行函數中具體的實參進行和形參進行綁定,形成字典的形式 func_type = sig.bind(*args, **kwargs).arguments.items() print(func_type) # 循環形參和實參字典的items()形式 for name, obj in func_type: if name in bind_types: # 判斷實參是否是指定類型數據 if not isinstance(obj, bind_types[name]): raise TypeError('%s must be %s' % (name, bind_types[name])) # 假如函數有返回值,通過此方法返回函數的返回值 res = func(*args, **kwargs) return res return wrapper return out_wrapper # 通過裝飾器實現對函數參數進行類型檢查 @check_type(str, int, float) def func(name, age, height): print(name, age, height) if __name__ == '__main__': # 正常數據 func('bei_men', 18, 1.75) # 錯誤數據 func('bei_men', '18', 1.75)
案例:
為分析程序內哪些函數執行時間開銷較大,我們需定義一個帶timeout參數的裝飾器
需求:
統計被裝飾函數的運行時間
時間大於timeout時,將此次函數調用記錄到log日志中
運行時可以修改timeout的值
如何解決這個問題?
- 定義一個裝飾器,計算函數執行時間,並與timeout比較,當大於timeout時候,通過logging模塊打印出日志信息
- 在包裹函數中添加一個函數,通過這個函數來修改timeout變量
- 在python3中用nonlocal來聲明嵌套作用域中的變量引用,在python2中可以通過把timeout參數變成列表,通過列表索引來進行改值
#!/usr/bin/python3 __author__ = 'beimenchuixue' __blog__ = 'http://www.cnblogs.com/2bjiujiu/' import time import logging from random import randint def run_time(timeout): """ 定義檢查函數運行時間,並打印對應函數運行時間超出設定時間日志,並支持更改timeout """ # python2 # timeout = [timeout] # 真正包裹函數 def out_wrapper(func): def wrapper(*args, **kwargs): start_time = time.time() result = func(*args, **kwargs) used_time = time.time() - start_time # 對於超出timeout的函數進行日志打印 if used_time > timeout: msg = '%s: %s > %s' % (func.__name__, used_time, timeout) logging.warn(msg) # python2 # if used_time > timeout[0]: # msg = '%s: %s > %s' % (func.__name__, used_time, timeout[0]) # logging.warn(msg) # return result return result # 設置timeout參數值 def set_timeout(value): # 聲明嵌套域變量,可以更改,python2通過把列表形式進行更改 nonlocal timeout timeout = value # 定義接口 wrapper.set_timeout = set_timeout # python2 # def set_timeout(value): # timeout[0] = value # wrapper.set_timeout = set_timeout return wrapper return out_wrapper @run_time(1.5) def func(): # 隨機有50%的幾率程序沉睡1秒 while randint(0, 1): time.sleep(1) print('func_run') if __name__ == "__main__": for _ in range(10): func() print('_' * 50) # 更改run_time裝飾器中timeout參數 func.set_timeout(2) for _ in range(10): func()
如何邏輯整理?
-- 3層 :一層獲得value,二層偷梁換柱,三層邏輯處理
-- 2層:一層偷梁換柱,二層邏輯處理