https://xiaozhuanlan.com/gopy
https://xiaozhuanlan.com/topic/9820573614
Python 2 - 高級用法 - 裝飾器
一談到 裝飾器,就離不開
閉包
閉包
閉包就是能夠讀取其他函數內部變量的函數。這個被引用的自由變量將和這個函數一同存在,即使已經離開了創造它的環境也不例外。
作用域
瞭解 閉包之前,先來看一下作用域
作用域是程序運行時變量可被訪問的範圍,定義在函數內的變量是局部變量,局部變量的作用範圍只能是函數範圍內,它不能在函數外引用。當函數結束時,變量也會跟隨函數結束而變得不可以被訪問。
## 作用域
def test_1():
test = 1
test_1()
print(test)
/gopy # python Python/Python2/1-1.py
Traceback (most recent call last):
File "Python/Python2/1-1.py", line 10, in <module>
print(test)
NameError: name 'test' is not defined
當你在 test_1()
函數外部嘗試訪問作用域中的變量 test
時,此時 Python解釋器會向你報告錯誤信息: NameError: name 'test' is not defined
。說明此時的 test
變量並不能在test_1()
外部被訪問到。
閉包
你在函數 A 中定義了 函數 B,並將 B 作爲 A 的返回值返回, B 又使用了 A 中定義了的 變量,此時,可以形成了閉包。
# 閉包的形式
def A():
a = 1
def B():
print(a)
return B
下面的例子說明了閉包
當test()
函數 和temp = []
變量離開了定義它的函數test_1()
後,依然可以被test()
所保留。
## 閉包
def test_1():
temp = []
t = 1
# 定義在函數內部的函數
def test():
temp.append(t) # 使用了上層 test_1 中定義的局部變量:temp
print(f"{temp}.append({t})")
return test # 注意,這裏返回的是 test 函數
func = test_1() # 這裏獲取 test_1 中定義的 test
func() # 這裏可以看到 test_1 的局部變量 temp 變成了 [1]
func() # 說明可以通過 test 去訪問 temp 變量 和 t 變量
func()
# 輸出
/gopy # python Python/Python2/1-2.py
[1].append(1)
[1, 1].append(1)
[1, 1, 1].append(1)
所以,函數 test()
是如何知道變量 temp
和 t
的呢?原來在 test
的屬性中存在了一個用來存儲相關內容的列表 __closure__
。
for i in func.__closure__:
print(i.cell_contents)
這下我們可以看到 閉包 通過 __closure__
將 函數 test
與 變量 temp
和 t
綁定在了一起。
1
[1, 1, 1]
裝飾器
瞭解了閉包,裝飾器就很好理解了。裝飾器其實是閉包的一種特殊形式。
原始工作
現在有一個工作方法work
,用來輸出工作狀態i am working...
並返回 100.
## 工作中
def work():
work_time = 100
print('i am working ...')
return work_time
gopy # python Python/Python2/1-3.py
i am working ...
這時,突然你想增加一個功能,統計work
的實際工作時間,最簡單的方法是更改 work 的源碼,增加計算時間的代碼。
## 計算時間的工作
import time
def work_1():
work_time = 100
start = time.time()
print('i am working ...')
stop = time.time()
print(f'working time is : {stop - start}')
return work_time
i am working ...
working time is : 2.5272369384765625e-05
這只是只有一個 work
的情況,如果有幾十個上百個 work
的話,也要每個都去改嗎?這時,裝飾器上線了!
## 裝飾器:計算時間
import time
def work_time_cal(func): # 定義裝飾器,func 爲變量
def wrap(): # 裝飾器內部閉包函數
start = time.time()
result =func() # 運行 func
stop = time.time()
print(f'working time is : {stop - start}')
return result
return wrap # 返回閉包函數
## 工作中
@work_time_cal ## python 中的裝飾器語法爲 @ + 裝飾器名稱
def work_2():
work_time = 100
print('i am working 2 ...')
return work_time
我們打印下work_2
:
print(work_2)
# <function work_time_cal.<locals>.wrap at 0x10314e510>
果然,work_2
竟然變成了work_time_cal.wrap
!說明 裝飾器 @ 起作用了,那我們來看下它的 __closure__
中綁定的是什麼?
print(work_2.__closure__[0].cell_contents)
# <function work_2 at 0x10314e6a8>
原來是我們的 work_2
。也就是 @work_time_cal
把 work_2
作爲 func
傳入了 wrap
。最後 work_2
查看的時候變成了 wrap
。
那這個時候,我們運行 work_2
的話,其實運行的是 wrap()
閉包。而 wrap
計算了開始結束時間,並輸出,然後返回了 work_2
的結果。
def wrap(): # 裝飾器內部閉包函數
start = time.time()
result =func() # 運行 func
stop = time.time()
print(f'working time is : {stop - start}')
return result
i am working 2 ...
working time is : 5.7220458984375e-06
效果是不是與在函數中直接修改是一樣的?所以 裝飾器是一種在不改變原有函數內部代碼的情況下,增加功能的一種方法。
現在,我們也發現了,其實 @ + 裝飾器的作用是,將 下面的函數名作爲參數,傳到了裝飾器中,作爲裝飾器的參數。
類裝飾器
類具有一個方法__call__
,只要實現了這個方法的,那麼這個類的實例都是可以被調用的。python 中萬物皆對象,所以我們來看下平時使用的方法有沒有 __call__
呢,他也時可調用的。
>>> def test():
... print()
...
>>> dir(test)
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
>>> test.__call__
<method-wrapper '__call__' of function object at 0x7fd009e71bf8>
我們在 test
中的屬性中看到了__call__
方法,他是一個 method-wrapper 類型。那麼我們調用test()
的時候,實際上調用的也是 test.__call__()
。所以通過 __call__
提供了類作爲裝飾器的方法。
類裝飾器的實現實際上是通過 __init__
在創建的時候傳入被裝飾函數作爲參數,創建類的實例,然後類具有的 __call__
作爲實際運行的方法。
class WorkTimeCal12(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print(args)
print(**kwargs)
return self.func()
@WorkTimeCal12
def work_12():
return 100
這裏的 @WorkTimeCal12 其實執行的是 WorkTimeCal12(work_12),最後獲得的實例 self.func = work_12。
所以我們來看下 被裝飾後的work_12
是什麼類型呢?
>>> print(work_12)
<test.WorkTimeCal12 object at 0x7fd009e9b2b0>
是 WorkTimeCal12 d的實例對象,所以運行的是這個對象的__call__
。它即等同於以下代碼:
def work_13():
return 100
worktimecal12 = WorkTimeCal12(work_13)
worktimecal12()
帶參數的類裝飾器類型
被裝飾方法不帶參數,類裝飾器帶參數
# 類裝飾器,裝飾器帶參數,方法不帶參數
class WorkTimeCal13(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print(args)
print(**kwargs)
return self.func()
@WorkTimeCal13
def work_13():
return 100
work_13()
print(work_13)
多個裝飾器的疊加使用與運行次序
多個裝飾器的疊加使用
# 多個裝飾器疊加
def test1(func):
def wrap():
print("i am test 1 begin")
rev = func()
print("i am test 1 end")
return rev
return wrap
def test2(func):
def wrap():
print("i am test 2 begin")
rev = func()
print("i am test 2 end")
return rev
return wrap
@test1
@test2
def test_3():
print("i am test 3")
test_3()
<function test1.<locals>.wrap at 0x1074b5a60>
i am test 1 begin
i am test 2 begin
i am test 3
i am test 2 end
i am test 1 end
多個裝飾器的運行次序是什麼呢?
通過結果可以看到,實際的運行是這樣的:
test_1 begin
test_2 begin
test_3 begin - end
test_2 end
test_1 end
所以,疊加裝飾器,實際上是離方法最近的裝飾器 test_2
,在裝飾完方法 test_3
後,返回的 test_2.wrap
又被 test_1
裝飾,返回了 test_1.wrap
。
由此可知,疊加裝飾器的運行次序實際上是由最外層的裝飾器依次向內部運行,最後執行被裝飾的方法。
functools.wrap
被裝飾的方法被修改了成了裝飾器方法的返回值,那麼如何保留之前的方法屬性呢?答案就是 functools.wrap
裝飾器
# functools.wrap
import functools
def testfunc(func):
@functools.wraps(func)
def wrap():
return func()
return wrap
@testfunc
def test_wrap():
print('test wrap')
print(test_wrap)
test_wrap()
<function test_wrap at 0x10fc9dbf8>
test wrap
那麼這個名稱相同的函數,還是原來的函數麼?
print(test_wrap)
#<function test_wrap at 0x103d47bf8>
print(test_wrap.__closure__[0].cell_contents)
#<function test_wrap at 0x103d47b70>
它們並不相同,原來是名字修改成了被裝飾函數名, __closure__
存儲的還是被裝飾函數。
其他類型的裝飾器
帶有確定參數的裝飾器
注意,固定參數和不定參數的區別只是形式問題,只要正常傳入即可,結構都是相同的
裝飾器無參數,被裝飾的函數有固定的參數
## 裝飾器:計算時間 - 被裝飾的函數有參數:固定參數,裝飾器無參數
def work_time_cal_2(func):
def wrap(param1, param2): # 裝飾器內部閉包函數,帶兩個固定參數
start = time.time()
result = func(param1, param2) # 運行 func
stop = time.time()
print(f'working time is : {stop - start}')
return result
return wrap # 返回閉包函數
@work_time_cal_2
def work_2(param1, param2):
print(f"params is {param1} and {param2}")
return 100
work_2(123, 245)
#params is 123 and 245
#working time is : 1.2874603271484375e-05
裝飾器有固定參數,被裝飾的函數沒有參數
## 裝飾器:計算時間 - 被裝飾的函數沒有參數,裝飾器有參數:固定參數
def work_time_cal_4(param1):
print(f"work time cal 4's params is : {param1}")
def wrap_func(func): # 裝飾器內部閉包函數 1 : 參數是 func
def wrap(): # 裝飾器內部閉包函數 2 : 運行的是具體的 func
start = time.time()
result = func() # 運行 func
stop = time.time()
print(f'working time is : {stop - start}')
return result
return wrap # 返回閉包函數 2
return wrap_func # 返回閉包函數 1
@work_time_cal_4(1)
def work_4():
return 100
work_4()
#work time cal 4's params is : 1
#working time is : 9.5367431640625e-07
裝飾器有固定參數,被裝飾的函數也有固定參數
## 裝飾器:計算時間 - 被裝飾的函數有參數:固定參數,裝飾器有參數:固定參數
def work_time_cal_6(param1):
print(f"work time cal 6's params is : {param1}")
def wrap_func(func): # 裝飾器內部閉包函數 1 : 參數是 func
def wrap(in1, in2): # 裝飾器內部閉包函數 2 : 運行的是具體的 func
print(f"work time cal 6-wrap's params is {in1} and {in2}")
start = time.time()
result = func(in1, in2) # 運行 func
stop = time.time()
print(f'working time is : {stop - start}')
return result
return wrap # 返回閉包函數 2
return wrap_func # 返回閉包函數 1
@work_time_cal_6(1)
def work_6(in1, in2):
return in1 + in2
work_6(1, 2)
#work time cal 6's params is : 1
#work time cal 6-wrap's params is 1 and 2
#working time is : 1.1920928955078125e-06
帶有不確定參數的裝飾器
裝飾器無參數,被裝飾的函數有固定參數
## 裝飾器:計算時間 - 被裝飾的函數有參數:不固定參數,裝飾器無參數
def work_time_cal_3(func):
def wrap(*args, **kwargs): # 裝飾器內部閉包函數,帶不固定參數
start = time.time()
result = func(*args, **kwargs) # 運行 func
stop = time.time()
print(f'working time is : {stop - start}')
return result
return wrap # 返回閉包函數
@work_time_cal_3
def work_3(*args, **kwargs):
print(f"params is {args} and {kwargs}")
return 100
#params is (1, 2, 3, 4, 5) and {'p': -1, 'p1': -2}
#working time is : 1.3113021850585938e-05
裝飾器有不固定參數,被裝飾的函數無參數
## 裝飾器:計算時間 - 被裝飾的函數無參數,裝飾器有參數:不固定參數
def work_time_cal_5(*args, **kwargs):
print(f"work time cal 5's params is : {args} and {kwargs}")
def wrap_func(func): # 裝飾器內部閉包函數 1 : 參數是 func
def wrap(): # 裝飾器內部閉包函數 2 : 運行的是具體的 func
start = time.time()
result = func() # 運行 func
stop = time.time()
print(f'working time is : {stop - start}')
return result
return wrap # 返回閉包函數 2
return wrap_func # 返回閉包函數 1
@work_time_cal_5(1, 2, 3, 4, p=1, p1=2)
def work_5():
return 100
work_5()
#work time cal 5's params is : (1, 2, 3, 4) and {'p': 1, 'p1': 2}
#working time is : 7.152557373046875e-07
類方法裝飾器
類方法裝飾器不帶參數,類方法不帶參數
# 裝飾器:類方法裝飾器,類方法不帶參數
def work_time_cal_8(func): # 帶有 self 來表示 類
def wrap(self): # 其實這裏 self 也是參數,只不過是 類的 self,他也可以叫別的名字,只要位於第一位參數即可
start = time.time()
result = func(self) # 運行 func 時也是調用 func(self)
stop = time.time()
print(f'class working time is : {stop - start}')
return result
return wrap
class A(object):
def __init__(self):
pass
@work_time_cal_8
def work_8(self):
return 100
a=A()
a.work_8()
類方法裝飾器不帶參數,類方法帶參數:固定參數
# 裝飾器:類方法裝飾器,類方法帶參數
def work_time_cal_9(func): # 帶有 self 來表示 類
def wrap(self, param1, param2): # 其實這裏 self 也是參數,只不過是 類的 self,他也可以叫別的名字,只要位於第一位參數即可
print(f'work time cal 9-wrap param is : {param1} and {param2}')
start = time.time()
result = func(self, param1, param2) # 運行 func 時也是調用 func(self),後面跟參數即可
stop = time.time()
print(f'class working time is : {stop - start}')
return result
return wrap
class B(object):
def __init__(self):
pass
@work_time_cal_9
def work_9(self, param1, param2):
return 100
a=B()
a.work_9(1, 2)
裝飾器方法帶參數,類方法不帶參數
# 裝飾器:裝飾器方法帶參數,類方法不帶參數
def work_time_cal_10(param1, param2): # 帶有 self 來表示 類
print(f"work time cal 10's param is {param1} and {param2}")
def wrap_class(func):
def wrap(self): # 其實這裏 self 也是參數
start = time.time()
result = func(self) # 運行 func 時也是調用 func(self)
stop = time.time()
print(f'class working time is : {stop - start}')
return result
return wrap
return wrap_class
class C(object):
def __init__(self):
pass
@work_time_cal_10(1, 2)
def work_10(self):
return 100
c=C()
c.work_10()
裝飾器方法帶參數,類方法帶參數,都是不定參數
# 裝飾器:裝飾器方法帶參數,類方法帶參數,都是不定參數
def work_time_cal_11(*args, **kwargs): # 帶有 self 來表示 類
print(f"work time cal 10's param is {args} and {kwargs}")
def wrap_class(func):
def wrap(self, *args1, **kwargs1): # 其實這裏 self 也是參數
start = time.time()
result = func(self, *args1, **kwargs1) # 運行 func 時也是調用 func(self)
stop = time.time()
print(f'class working time is : {stop - start}')
return result
return wrap
return wrap_class
class D(object):
def __init__(self):
pass
@work_time_cal_11(1, 2, p=1, p1=2)
def work_11(self, *args, **kwargs):
print(f"work 11's params is {args} and {kwargs}")
return 100
d = D()
d.work_11(3, 4, p=123, p1=234)