此文多是有史以來最全的關於Python裝飾器的Blog了...面試
函數名是⼀個變量,但它是⼀個特殊的變量。與括號配合能夠執⾏函數的變量。緩存
查看函數名的內存地址:閉包
def func(): print('呵呵') print(func) # <function func at 0x10983c048>
def func(): print('呵呵') a = func # 把函數當成變量賦值給另一個變量 a() # 經過變量a調用函數
def func1(): print('func1') def func2(): print('func2') def func3(): print('func3') def func4(): print('func4') list1 = [func1, func2, func3, func4] for i in list1: i()
def func1(): print('func1') def func2(arg): print('start') arg() # 執行傳遞進來的arg print('end') func2(func1) # 把func1當成參數傳遞給func2
def func1(): print('這裏是func1') def func2(): print('這裏是func2') return func2 # 把func2當成返回值返回 ret = func1() # 調用func1,把返回值賦值給ret ret() # 調用ret
首先咱們來看一個例子:函數
def func1(): name = '張三' def func2(arg): print(arg) func2(name) func1()
理解了上面的例子,咱們再看一個例子:工具
def func1(): name = '張三' def func2(): print(name) # 可以訪問到外層做用域的變量 func2() func1()
最後再看一個例子:spa
def func1(name): def func2(): print(name) # 可以訪問到外層做用域的變量 func2() func1('張三')
一個內層函數中,引用了外層函數(非全局)的變量,這個內層函數就能夠成爲閉包。設計
在Python中,咱們可使用__closure__來檢測函數是不是閉包。code
def func1(): name = '張三' def func2(): print(name) # 可以訪問到外層做用域的變量 func2() print(func2.__closure__) # (<cell at 0x1036c7438: str object at 0x10389d088>,) func1() print(func1.__closure__) # None
問題來了,咱們如何在函數外邊調用函數內部的函數呢?orm
固然是把內部函數當成返回值返回了。blog
def func1(): name = '張三' def func2(): print(name) return func2 # 把內部函數當成是返回值返回 ret = func1() # 把返回值賦值給變量ret ret() # 調用內部函數
內部函數固然還可包含其餘的函數,多層嵌套的道理都是同樣的。
def func1(): def func2(): def func3(): print('func3') return func3 return func2 ret1 = func1() # func2 ret2 = ret1() # func3 ret2()
接下來咱們看下面這個例子,來更深入的理解一下閉包的含義:
def print_msg(msg): # 這是外層函數 def printer(): # 這是內層函數 print(msg) return printer # 返回內層函數 func = print_msg("Hello") func()
如今咱們進行以下操做:
>>> del print_msg >>> func() Hello >>> print_msg("Hello") Traceback (most recent call last): ... NameError: name 'print_msg' is not defined
咱們知道若是⼀個函數執⾏完畢,則這個函數中的變量以及局部命名空間中的內容都將會被銷燬。在閉包中內部函數會引用外層函數的變量,並且這個變量將不會隨着外層函數的結束而銷燬,它會在內存中保留。
也就是說,閉包函數能夠保留其用到的變量的引用。
# 編寫代碼實現func函數,使其實現如下效果: foo = func(8) print(foo(8)) # 輸出64 print(foo(-1)) # 輸出-8
在說裝飾器以前,咱們先說⼀個軟件設計的原則: 開閉原則, ⼜被成爲開放封閉原則。
開放封閉原則是指對擴展代碼的功能是開放的,可是對修改源代碼是封閉的。這樣的軟件設計思路能夠保證咱們更好的開發和維護咱們的代碼。
咱們先來寫一個例子,模擬一下女媧造人:
def create_people(): print('女媧真厲害,捏個泥吹口氣就成了人!') create_people()
好吧,如今問題來了。上古時期啊,天氣很不穩定,這個時候忽然大旱三年。女媧再去捏人啊,由於太乾了就捏不到一起去了,須要在捏人以前灑點水才行。
def create_people(): print('灑點水') print('女媧真厲害,捏個泥吹口氣就成了人!') create_people()
這不就搞定了麼?可是呢,咱們是否是違背了開放封閉原則呢?咱們是添加了新的功能,可是咱們是直接修改了源代碼。在軟件開發中咱們應該對直接修改源代碼是謹慎的。
好比,女媧爲了防止浪費,想用剩下點泥巴捏個雞、鴨、鵝什麼的,也須要灑點水。那咱們能在每一個造雞、造鴨、造鵝函數的源代碼中都手動添加代碼麼?確定是不現實的。
怎麼辦?再寫一個函數不就OK了麼?
def create_people(): print('女媧真厲害,捏個泥吹口氣就成了人!') def create_people_with_water(): print('灑點水') create_people() create_people_with_water()
不讓我直接修改源代碼,那我從新寫一個函數不就能夠了嗎?
可是,你有沒有想過一個問題,女媧造人也很累的,她後來開了不少分店,每家分店都是調用了以前的create_people函數造人,那麼你修改了以後,是否是全部調用原來函數的人都須要修改調用函數的名稱呢?很麻煩啊!!!
總結一句話就是如何在不改變函數的結構和調用方式的基礎上,動態的給函數添加功能?
def create_people(): print('女媧真厲害,捏個泥吹口氣就成了人!') def a(func): def b(): print('灑點水') func() return b ret = a(create_people) ret()
利用閉包函數不就能夠了麼?
可是,你這最後調用的是ret啊,不仍是改變了調用方式麼?
再往下看:
def create_people(): print('女媧真厲害,捏個泥吹口氣就成了人!') def a(func): def b(): print('灑點水') func() return b create_people = a(create_people) create_people()
上面這段代碼是否是完美解決了咱們的問題呢?
看一下它的執行過程吧:
咱們巧妙的使用閉包實現了,把一個函數包裝了一下,而後再賦值給原來的函數名。
上面的代碼就是一個裝飾器的雛形,Python中針對於上面的功能提供了一個快捷的寫法,俗稱裝飾器語法糖。
使用裝飾器語法糖的寫法,實現一樣功能的代碼以下:
def a(func): def b(): print('灑點水') func() return b @a # 裝飾器語法糖 def create_people(): print('女媧真厲害,捏個泥吹口氣就成了人!') create_people()
若是被裝飾的函數有返回值,咱們應該怎麼處理呢?
請看下面的示例:
def foo(func): # 接收的參數是一個函數名 def bar(): # 定義一個內層函數 print("這裏是新功能...") # 新功能 r = func() # 在內存函數中拿到被裝飾函數的結果 return r # 返回被裝飾函數的執行結果 return bar # 定義一個有返回值的函數 @foo def f1(): return '嘿嘿嘿' # 調用被裝飾函數 ret = f1() # 調用被裝飾函數並拿到結果 print(ret)
def foo(func): # 接收的參數是一個函數名 def bar(x, y): # 這裏須要定義和被裝飾函數相同的參數 print("這裏是新功能...") # 新功能 func(x, y) # 被裝飾函數名和參數都有了,就能執行被裝飾函數了 return bar # 定義一個須要兩個參數的函數 @foo def f1(x, y): print("{}+{}={}".format(x, y, x+y)) # 調用被裝飾函數 f1(100, 200)
被裝飾的函數能夠帶參數,裝飾器一樣也能夠帶參數。
回頭看咱們上面寫得那些裝飾器,它們默認把被裝飾的函數當成惟一的參數。可是呢,有時候咱們須要爲咱們的裝飾器傳遞參數,這種狀況下應該怎麼辦呢?
接下來,咱們就一步步實現帶參數的裝飾器:
首先咱們來回顧下上面的代碼:
def f1(func): # f1是咱們定義的裝飾器函數,func是被裝飾的函數 def f2(*arg, **kwargs): # *args和**kwargs是被裝飾函數的參數 func(*arg, **kwargs) return f2
從上面的代碼,咱們發現了什麼?
個人裝飾器若是有參數的話,沒地方寫了…怎麼辦呢?
仍是要使用閉包函數!
咱們須要知道,函數除了能夠嵌套兩層,還能嵌套更多層:
# 三層嵌套的函數 def f1(): def f2(): name = "張三" def f3(): print(name) return f3 return f2
嵌套三層以後的函數調用:
f = f1() # f --> f2 ff = f() # ff --> f3 ff() # ff() --> f3() --> print(name) --> 張三
注意:在內部函數f3中可以訪問到它外層函數f2中定義的變量,固然也能夠訪問到它最外層函數f1中定義的變量。
# 三層嵌套的函數2 def f1(): name = '張三' def f2(): def f3(): print(name) return f3 return f2
調用:
f = f1() # f --> f2 ff = f() # ff --> f3 ff() # ff() --> f3() --> print(name) --> 張三
好了,如今咱們就能夠實現咱們的帶參數的裝飾器函數了:
# 帶參數的裝飾器須要定義一個三層的嵌套函數 def d(name): # d是新添加的最外層函數,爲咱們原來的裝飾器傳遞參數,name就是咱們要傳遞的函數 def f1(func): # f1是咱們原來的裝飾器函數,func是被裝飾的函數 def f2(*arg, **kwargs): # f2是內部函數,*args和**kwargs是被裝飾函數的參數 print(name) # 使用裝飾器函數的參數 func(*arg, **kwargs) # 調用被裝飾的函數 return f2 return f1
上面就是一個帶參裝飾器的代碼示例,如今咱們來寫一個完整的應用:
def d(a=None): # 定義一個外層函數,給裝飾器傳參數--role def foo(func): # foo是咱們原來的裝飾器函數,func是被裝飾的函數 def bar(*args, **kwargs): # args和kwargs是被裝飾器函數的參數 # 根據裝飾器的參數作一些邏輯判斷 if a: print("歡迎來到{}頁面。".format(a)) else: print("歡迎來到首頁。") # 調用被裝飾的函數,接收參數args和kwargs func(*args, **kwargs) return bar return foo @d() # 不給裝飾器傳參數,使用默認的'None'參數 def index(name): print("Hello {}.".format(name)) @d("電影") # 給裝飾器傳一個'電影'參數 def movie(name): print("Hello {}.".format(name)) if __name__ == '__main__': index('張三') movie('張三')
被裝飾的函數最終都會失去原本的__doc__等信息, Python給咱們提供了一個修復被裝飾函數的工具。
def a(func): @wraps(func) def b(): print('灑點水') func() return b @a # 裝飾器語法糖 def create_people(): """這是一個女媧造人的功能函數""" print('女媧真厲害,捏個泥吹口氣就成了人!') create_people() print(create_people.__doc__) print(create_people.__name__)
同一個函數能夠被多個裝飾器裝飾,此時須要注意裝飾器的執行順序。
def foo1(func): print("d1") def inner1(): print("inner1") return "<i>{}</i>".format(func()) return inner1 def foo2(func): print("d2") def inner2(): print("inner2") return "<b>{}</b>".format(func()) return inner2 @foo1 @foo2 def f1(): return "Hello Andy" # f1 = foo2(f1) ==> print("d2") ==> f1 = inner2 # f1 = foo1(f1) ==> print("d1") ==> f1 = foo1(inner2) ==> inner1 ret = f1() # 調用f1() ==> inner1() ==> <i>inner2()</i> ==> <i><b>inner1()</b></i> ==> <i><b>Hello Andy</b></i> print(ret)
咱們除了可使用函數裝飾函數外,還能夠用類裝飾函數。
class D(object): def __init__(self, a=None): self.a = a self.mode = "裝飾" def __call__(self, *args, **kwargs): if self.mode == "裝飾": self.func = args[0] # 默認第一個參數是被裝飾的函數 self.mode = "調用" return self # 當self.mode == "調用"時,執行下面的代碼(也就是調用使用類裝飾的函數時執行) if self.a: print("歡迎來到{}頁面。".format(self.a)) else: print("歡迎來到首頁。") self.func(*args, **kwargs) @D() def index(name): print("Hello {}.".format(name)) @D("電影") def movie(name): print("Hello {}.".format(name)) if __name__ == '__main__': index('張三') movie('張三')
咱們上面全部的例子都是裝飾一個函數,返回一個可執行函數。Python中的裝飾器除了能裝飾函數外,還能裝飾類。
可使用裝飾器,來批量修改被裝飾類的某些方法:
# 定義一個類裝飾器 class D(object): def __call__(self, cls): class Inner(cls): # 重寫被裝飾類的f方法 def f(self): print('Hello 張三.') return Inner @D() class C(object): # 被裝飾的類 # 有一個實例方法 def f(self): print("Hello world.") if __name__ == '__main__': c = C() c.f()
舉個實際的應用示例:
咱們把類中的一個只讀屬性定義爲property屬性方法,只有在訪問它時才參與計算,一旦訪問了該屬性,咱們就把這個值緩存起來,下次再訪問的時候無需從新計算。
class lazyproperty: def __init__(self, func): self.func = func def __get__(self, instance, owner): if instance is None: return self else: value = self.func(instance) setattr(instance, self.func.__name__, value) return value import math class Circle: def __init__(self, radius): self.radius = radius @lazyproperty def area(self): print('計算面積') return math.pi * self.radius ** 2 c1 = Circle(10) print(c1.area) print(c1.area)