函數也是對象,可調用的對象python
函數能夠做爲普通變量、參數、返回值等等設計模式
數學概念 y=g(f(x))緩存
在數學和計算機科學中,高階函數應當是至少知足下面一個條件的函數app
接受一個或多個函數做爲參數分佈式
輸出一個函數ide
內建高階函數函數
sorted(iterable[, key][, reverse])工具
排序spa
filter(function, iterable) --> filter object設計
過濾數據
map(func, *iterables) --> map object
映射
sorted(iterable[, key][, reverse]) 排序
返回一個新的列表,對一個可迭代對象的全部元素排序,排序規則爲key定義的函數,reverse表示是否排序翻轉
filter(function, iterable)
過濾可迭代對象的元素,返回一個迭代器
function一個具備一個參數的函數,返回bool
map(function, *iterables) --> map object
對多個可迭代對象的元素按照指定的函數進行映射,返回一個迭代器
柯里化Currying
指的是將原來接受兩個參數的函數變成新的接受一個參數的函數的過程。新的函數返回一個以原有第二個參數爲參數的函數
z = f(x, y) ==> z = f(x)(y)
舉例
將加法函數柯里化
def add(x, y): return x + y
轉換以下
def add(x): def _add(y): return x+y return _add add(5)(6)
經過嵌套函數就能夠把函數轉換成柯里化函數
裝飾器
在OOP設計模式中,裝飾器屬於一種裝飾模式
裝飾器語法是一個語法糖
def logger(func): def wrapper(*args, **kwargs): print('call ' + func.__name__) return func(*args, **kwargs) return wrapper
@logger # 等價於add = logger(add) def add(x,y): return x + y
裝飾器(無參)
它是一個函數
函數做爲它的形參
返回值也是一個函數
可使用@functionname方式,簡化調用
裝飾器是高階函數,但裝飾器是對傳入函數的功能的裝飾(功能加強)
列入一個加法函數 def add (x,y) return x,y
這個函數只是簡單定義函數,可是若是想要函數增長打印功能
def add (x,y): return x + y def logger (fn): print (" call function {} .x={} y={}".format(fn.__name__ ,4 ,5)) ret = fn(3,5) return ret print ('result = {}'.format(logger(add)))
文檔字符串
在函數語句塊的第一行,且習慣是多行的文本,因此多使用三引號
慣例是首字母大寫,第一行寫概述,空一行,第三行寫詳細描述
可使用特殊屬性doc訪問這個文檔
裝飾器反作用
原函數對象的屬性都被替換了(執行後返回的是wrapper的屬性),而使用裝飾器,咱們的需求是查看被封裝函數的屬性
解決方法:
1.提供一個函數,被封裝函數屬性 ==copy==> 包裝函數屬性 def copy_properties(src, dst): # 能夠改形成裝飾器 dst.__name__ = src.__name__ dst.__doc__ = src.__doc__
若是除了加法還想增長減法到話
能夠寫爲
def add (x,y): return x + y def sub (x,y): return x - y def logger (fn,x,y): print (" call function {} .x={} y={}".format(fn.__name__ ,x,y)) ret = fn(x,y) return ret print ('result = {}'.format(logger(add,3,2))) print ('result = {}'.format(logger(sub,4,5))
)
並且增長了能夠直接調用函數
若是想要變得更加同的話
目前傳參只有倆種
關鍵字傳參**kwargs
位置傳參 *args
第一個無參裝飾器
def logger (fn): def wrapper (*args,**kwargs): print (" call function {} .x={} y={}".format(fn.__name__ ,*args,**kwargs)) ret = fn(*args,**kwargs) return ret return inner @logger # =>add = logger (add) def add (x,y): return x + y #add = wrapper (add) ret = add (4,5) print (ret)
若是函數在增長功能函數的話
能夠以下
加一些計算時間功能
函數所消耗時間(注意要先定義一個函數)
import datetime import time def logger (fn): def wrapper (*args,**kwargs): print ("前面加強") start = datetime.datetime.now () #print (" call function {} .x={} y={}".format(fn.__name__ ,*args,**kwargs)) ret = fn(*args,**kwargs) delta = (datetime.datetime.now()-start).total_seconds() print ('Function {} took {}s'.format(fn.__name__,delta)) if delta >5: print ('so slow') else: print ('so fast') print ("後面加強") return ret return wrapper @logger # =>add = logger (add) def add (x,y): time.sleep (3) return x + y ret = add (4,5) print (ret)
文檔字符串
要求放在函數內部的第一行
文檔字符串使用慣例:它的首行簡述函數功能,第二行空行,第三行爲函數的具體描述。
帶參裝飾器
首先柯里化
import datetime import time def copy_porperties (src): def _copy(dest): dest.__name__ = src.__name__ dest.__doc__ = src.__doc__ return dest return _copy # return src def logger (fn): @copy_porperties(fn)#wrapper = copy_porperties(wrapper)是下面的wrapper = _copy(wrapper) # def copy_porperties (src,dest): # dest.__name__ = src.__name__ # dest.__doc__ = src.__doc__ def wrapper (*args,**kwargs): """The Function is wrapper""" print ("前面加強") start = datetime.datetime.now () #print (" call function {} .x={} y={}".format(fn.__name__ ,*args,**kwargs)) ret = fn(*args,**kwargs) delta = (datetime.datetime.now()-start).total_seconds() print ('Function {} took {}s'.format(fn.__name__,delta)) if delta >5: print ('so slow') else: print ('so fast') print ("後面加強") return ret #copy_porperties(fn)(wrapper) return wrapper @logger # =>add = logger (add) # w = logger (add) # copy_porperties(add,wrapper) def add (x,y): """The Function is add """ time.sleep(0) return x + y ret = add (4,5) print (ret) print(add.__name__) print (add.__doc__) #print ("0-------------------") #help (add)
經過copy_properties函數將被包裝函數的屬性覆蓋掉包裝函數
凡是被裝飾的函數都須要複製這些屬性,這個函數很通用
能夠將複製屬性的函數構建成裝飾器函數,帶參裝飾器
2.使用functools.update_wrapper(wrapper, wrapped)
import datetime, time, functools def logger(duration, func=lambda name, duration: print('{} took {}s'.format(name, duration))): def _logger(fn): def wrapper(*args,**kwargs): start = datetime.datetime.now() ret = fn(*args,**kwargs) delta = (datetime.datetime.now() - start).total_seconds() if delta > duration: func(fn.__name__, duration) return ret return functools.update_wrapper(wrapper, fn) return _logger @logger(5) # add = logger(5)(add) def add(x,y): time.sleep(1) return x + y print(add(5, 6), add.__name__, add.__wrapped__, add.__dict__, sep='\n')
3.使用@functools.wraps(wrapped)
import datetime, time, functools def logger(duration, func=lambda name, duration: print('{} took {}s'.format(name, duration))): def _logger(fn): @functools.wraps(fn) def wrapper(*args,**kwargs): start = datetime.datetime.now() ret = fn(*args,**kwargs) delta = (datetime.datetime.now() - start).total_seconds() if delta > duration: func(fn.__name__, duration) return ret return wrapper return _logger @logger(5) # add = logger(5)(add) def add(x,y): time.sleep(1) return x + y print(add(5, 6), add.__name__, add.__wrapped__, add.__dict__, sep='\n')
帶參裝飾器
它是一個函數 函數做爲它的形參
返回值是一個不帶參的裝飾器函數
使用@functionname(參數列表)方式調用
能夠看作在裝飾器外層又加了一層函數
將記錄的功能提取出來,這樣就能夠經過外部提供的函數來靈活的控制輸出 參數註解 形如:
def add(x:int , y:int) -> int : ''' :param x: int :param y: int :return: int ''' return x + y
Python 3.5引入
對函數的參數進行類型註解
對函數的返回值進行類型註解
只對函數參數作一個輔助的說明,並不對函數參數進行類型檢查
提供給第三方工具,作代碼分析,發現隱藏的bug
函數註解的信息,保存在annotations屬性中
鍵是參數名,值是class類型
變量註解使用較少 inspect模塊 signature(callable),返回一個Signature類對象。獲取簽名(函數簽名包含了一個函數的信息,包括函數名、它的參數類型、它所在的類和名稱空間及其餘信息) sig = signature(fn) params = sig.parameters
Signature類對象的parameters方法返回的是一個orderedDict,其key值爲fn的形參名,value值爲Parameter類對象 Parameter對象 保存在元組中,是隻讀的
name,參數的名字
annotation,參數的註解,可能沒有定義
default,參數的缺省值,可能沒有定義
empty,特殊的類,用來標記default屬性或者註釋annotation屬性的空值
kind,實參如何綁定到形參,就是形參的類型
POSITIONAL_ONLY,值必須是位置參數提供 #Python沒有實現次形參類型
POSITIONAL_OR_KEYWORD,值能夠做爲關鍵字或者位置參數提供
VAR_POSITIONAL,可變位置參數,對應*args
KEYWORD_ONLY,keyword-only參數,對應或者args以後的出現的非可變關鍵字參數
VAR_KEYWORD,可變關鍵字參數,對應**kwargs
業務應用
函數參數類型檢查
思路
函數參數的檢查,必定是在函數外
函數應該做爲參數,傳入到檢查函數中
檢查函數拿到函數傳入的實際參數,與形參聲明對比
annotations屬性是一個字典,其中包括返回值類型的聲明。假設要作位置參數的判斷,沒法和字典中的聲明對應。使用inspect模塊
inspet模塊提供獲取對象信息的函數,能夠檢查函數和類、類型檢查
代碼實現:
import inspect def check(fn): def wrapper(*args,**kwargs): sig = inspect.signature(fn) params = sig.parameters #是orderedDict values = list(params.values()) #可迭代對象轉化成列表,處理後也是有序的 for k,v in enumerate(args): #由於values、args都有序 if values[k].annotation is not inspect._empty and not isinstance(v,values[k].annotation): return '{}`s input is {} type , expected {}'.format(v,type(v),values[k].annotation) for k,v in kwargs.items(): #kwargs與params都爲字典形式,鍵值相同 if params[k].annotation is not inspect._empty and not isinstance(v, params[k].annotation): return '{}`s input is {} type , expected {}'.format(k,type(v),params[k].annotation) return fn(*args,**kwargs) return wrapper @check def add(x:int,y:int): return x + y
functools模塊
偏函數partial
把函數部分的參數固定下來,至關於爲部分的參數添加了一個固定的默認值,造成一個新的函數並返回
從partial生成的新函數,是對原函數的封裝
partial源代碼邏輯
def partial(func, *args, **keywords): def newfunc(*fargs, **fkeywords): # 包裝函數 newkeywords = keywords.copy() #將原函數關鍵字參數拷貝後, newkeywords.update(fkeywords) #將調用新函數時的關鍵字參數更新 return func(*(args + fargs), **newkeywords) #調用新函數時的參數是創建偏函數時給的參 #數,再各加上調用新函數時的參 newfunc.func = func # 保留原函數,注意:若是原函數已有裝飾器,func屬性內容有可能不是原函數名,而是裝飾器函數名 newfunc.args = args # 保留原函數的位置參數 newfunc.keywords = keywords # 保留原函數的關鍵字參數參數 return newfunc
partial()返回一個新函數newfunc,而newfunc返回原函數,其參數是是創建偏函數時給的參數,再各加上調用新函數時的參
LRU緩存裝飾器
@functools.lru_cache(maxsize=128, typed=False)
Least-recently-used裝飾器。lru,最近最少使用。cache緩存
若是maxsize設置爲None,則禁用LRU功能,而且緩存能夠無限制增加。當maxsize是二的冪時,LRU功能執行得最好
若是typed設置爲True,則不一樣類型的函數參數將單獨緩存。例如,f(3)和f(3.0)將被視爲具備不一樣結果的不一樣調用
經過一個字典緩存被裝飾函數的調用和返回值
若是全爲位置實參且位置與數值都相同,則視爲相同實參,或者全傳關鍵字參數則即便順序不一樣也視爲相同實參,即緩存能使用上;其餘狀況則皆視爲不一樣實參
lru_cache使用_make_key生成參數緩存
源碼:
def _make_key(args, kwds, typed, kwd_mark = (object(),), fasttypes = {int, str, frozenset, type(None)}, tuple=tuple, type=type, len=len): """Make a cache key from optionally typed positional and keyword arguments The key is constructed in a way that is flat as possible rather than as a nested structure that would take more memory. If there is only a single argument and its data type is known to cache its hash value, then that argument is returned without a wrapper. This saves space and improves lookup speed. """ key = args if kwds: key += kwd_mark for item in kwds.items(): key += item if typed: key += tuple(type(v) for v in args) if kwds: key += tuple(type(v) for v in kwds.values()) elif len(key) == 1 and type(key[0]) in fasttypes: return key[0] return _HashedSeq(key)
lru_cache裝飾器應用
使用前提
一樣的函數參數必定獲得一樣的結果
函數執行時間很長,且要屢次執行
本質是函數調用的參數=>返回值
缺點
不支持緩存過時,key沒法過時、失效
不支持清除操做
不支持分佈式,是一個單機的緩存
適用場景,單機上須要空間換時間的地方,能夠用緩存來將計算變成快速的查詢