1.在一個外函數中定義了一個內函數html
2.內函數裏運用了外函數的臨時變量,而不是全局變量python
3.而且外函數的返回值是內函數的引用。(函數名,內存塊地址,函數名指針..)編程
正確形式:閉包
def outer(): name = "dasfa" def inner(): print("inner func exec,get outer func name",name) return inner func = outer() func() #執行內聯中的函數"inner func exec,get outer func name dasfa"
案例一:內聯函數inner使用的是外部outer函數中的數據app
>>> def outer(): ... age = 10 ... print(id(age)) ... def inner(): ... print("age",age) ... print(id(age)) ... return inner ... >>> func = outer() 2012959808 >>> func() age 10 2012959808
案例二:是否能夠修改outer中的數據:ide
>>> def outer(): ... age = 10 ... print(id(age)) ... def inner(): ... age = 11 ... print(id(age)) ... return inner ... >>> func = outer() 2012959808 >>> func() 2012959840
就同python---基礎知識回顧(一)所說的傳參同樣,要想修改,那就須要將其中的變量設置爲可變類型(列表等)函數式編程
使用閉包的好處:含有本身的變量,不與全局變量相沖突。能夠不用傳參。函數
閉包都有__closure__屬性,會返回外部做用域中的變量信息。post
>>> def outer(): ... age = 10 ... print(id(age)) ... def inner(): ... print(id(age)) ... print(age) ... return inner ... >>> func = outer() 2012959808 >>> print(func.__closure__) (<cell at 0x0000000000CF8DC8: int object at 0x0000000077FB5440>,) #返回元組,如果有多個變量,都會顯示在這裏面,咱們均可以經過索引獲取 >>> print(func.__closure__[0]) #獲取地址 <cell at 0x0000000000CF8DC8: int object at 0x0000000077FB5440> >>> print(func.__closure__[0].cell_contents) #獲取值 10
注意:如果引用的是全局變量,獲取inner函數中將外部outer函數中的變量進行覆蓋後,那麼func.__closure__會爲空ui
from urllib.request import urlopen def outer(url): def get(): return urlopen(url).read() return get info = outer("https://www.baidu.com") print(info()) b'<html>\r\n<head>\r\n\t<script>\r\n\t\tlocation.replace(location.href.replace(" https://","http://"));\r\n\t</script>\r\n</head>\r\n<body>\r\n\t<noscript><meta http-equiv="refresh" content="0;url=http://www.baidu.com/"></noscript>\r\n</body >\r\n</html>'
就是用於拓展原來函數功能的一種函數,這個函數的特殊之處在於它的返回值也是一個函數,使用python裝飾器的好處就是在不用更改原函數的代碼前提下給函數增長新的功能。
開放封閉原則:雖然在這個原則是用的面向對象開發,可是也適用於函數式編程,簡單來講,它規定已經實現的功能代碼不容許被修改
(封閉:已實現的功能代碼塊,開放:對擴展開發)
裝飾器簡單實現:
def check_login(func): def inner(): #驗證1 #驗證2 #... return func() return inner @check_login def f1(): print("f1") @check_login def f2(): print("f2") @check_login def f3(): print("f3")
裝飾器:外部函數參數爲:被裝飾函數名,內部函數返回裝飾函數名。
特色:1.不修改被裝飾函數的調用方式 2.不修改被裝飾函數的源代碼。
其執行順序:@函數名是python中的一種語法形式,其內部會執行下面操做:
1.執行check_login函數,而且將@check_login下面的函數做爲參數,傳遞進去。等價於check_login(f1)
在內部會執行
def inner():
#驗證1
#驗證2
#...
return func() #func是參數,至關於傳入的函數名 return inner #返回的inner,inner表明的是函數名,不會去執行,會將這個函數傳遞到另一個函數中
2.將執行完的check_login函數返回值給@check_login下面修飾的函數的函數名:至關於對該函數進行了從新賦值(這個新的函數是通過修改驗證後的函數)
新f1 = inner #inner中添加了驗證代碼,而後執行了原來f1的邏輯代碼
3.執行新f1。即完成了驗證功能,有執行了原來F1的內容。並將其原來函數的返回值返回給業務調用
補充:多個裝飾器同時修飾時的調用順序:
>>> def check_1(func): ... def inner(): ... print("check_1") ... func() ... return inner ... >>> def check_2(func): ... def inner(): ... print("check_2") ... func() ... return inner ... >>> @check_1 index = check_1(check_2(index)) #後 ... @check_2 index = check_2(index) #先,而後將這個新的函數做爲參數傳遞 ... def index(): ... print("進入首頁") ... >>> index() check_1 check_2 進入首頁
... @check_2 index = check_2(index) #先,而後將這個新的函數做爲參數傳遞 ... def index(): ... print("進入首頁") index = def inner(): print("check_2") print("進入首頁")
最新的index函數是上面被check_2和check_1修飾過的函數.
check_1
check_2
進入首頁
外部函數是用來接收被裝飾函數的地址
內聯函數是用來接收參數(通常是與被修飾函數一致,由於inner函數中調用了被修飾函數,須要對其進行傳參)
def check_login(func): def inner(*args,**kwargs): return func() return inner
def check_func(func): def inner(arg1,arg2): #驗證 if 正確: return func(arg1,arg2) return inner @check_login def index(arg1,arg2): print("歡迎來到首頁")
def check_func(func): def inner(*args,**kwargs): #驗證 if 正確: return func(*args,**kwargs) return inner @check_login def index(*args,**kwargs): print("歡迎來到首頁")
def Before(request, kargs): print('before') def After(request, kargs): print('after') def Filter(before_func, after_func): def outer(main_func): def wrapper(request, kargs): before_result = before_func(request, kargs) if (before_result != None): return before_result main_result = main_func(request, kargs) if (main_result != None): return main_result after_result = after_func(request, kargs) if (after_result != None): return after_result return wrapper return outer @Filter(Before, After) #注意,這裏是先執行該外層函數而後返回outer函數名給@符號,進行處理。 def Index(request, kargs): print('index') Index("aa","bb")
裝飾器不只能夠是函數,還能夠是類,相比函數裝飾器,類裝飾器具備靈活度大、高內聚、封裝性等優勢。使用類裝飾器主要依靠類的__call__方法,當使用 @ 形式將裝飾器附加到函數上時,就會調用此方法。
class Foo(object): def __init__(self, func): self._func = func def __call__(self): print('class decorator runing') self._func() print('class decorator ending') @Foo # bar = Foo(bar) 傳入到__init__函數中 , 返回時使用__call__ def bar(): print('bar') bar() # Foo(bar)() # 結果 # class decorator runing # bar # class decorator ending
雖然使用裝飾器極大的複用了代碼,可是他有一個缺點,就是原函數的元信息不見了,好比__doc__,__name__,參數列表:
def check_login(func): def inner(): '''驗證信息''' print("驗證") func() return inner @check_login def f1(): '''首頁信息''' print("f1 exec") f1() print(f1.__doc__) #「驗證信息」,如果inner中也沒有__doc__信息,那麼這裏爲空 print(f1.__name__) #「inner」
這可不是咱們想要的信息。解決方案是:functool.wraps
functool.wraps自己也是一個裝飾器,他將原函數的元信息拷貝到裝飾器函數中,這使得裝飾器函數也有和原函數同樣的元信息了。
from functools import wraps def check_login(func): @wraps(func) def inner(): '''驗證信息''' print("驗證") func() return inner @check_login def f1(): '''首頁信息''' print("f1 exec") f1() print(f1.__doc__) #"首頁信息" print(f1.__name__) #"f1"
經常使用的內置裝飾器:
@staticmathod,@classmethod,@property