### 裝飾器定義
**在不改變原有函數代碼,且保持原函數調用方法不變的情況下,給原函數增加新的功能(或者給類增加屬性和方法)**
**核心思想**:用一個函數(或者類)去裝飾一個舊函數(或者類),造出一個新函數(或者新類)
**應用場景**:引入日志,函數執行時間的統計,執行函數前的準備工作,執行函數后的處理工作,權限校驗,緩存等
**語法規則**:在原有的函數上加上 @符,裝飾器會把下面的函數當作參數傳遞到裝飾器中,@符又被成為 語法糖
#### 1.裝飾器原型(閉包)
```python
# 1。 裝飾器的原型
### 利用閉包,把函數當作參數傳遞,并且在函數內去調用傳遞進來的函數,并返回一個函數
# 定義外函數,接收一個函數作為參數
def outer(f):
? ? # 定義內函數,并且在內函數中調用了外函數的參數
? ? def inner():
? ? ? ? print('我是外函數中的內函數1')
? ? ? ? f()
? ? ? ? print('我是外函數中的內函數2')
? ? return inner
# 定義普通函數
# def old():
#? ? print('我是一個普通的函數')
#
# # old()? # 作為普通函數直接調用
# old = outer(old)? # outer返回了inner函數,賦值給了old
# old()? ? ? ? ? ? # 此時再調用old函數時,等同于調用了 inner 函數
# 改為裝飾器用法
@outer? ? ? # 此處使用的@outer的語法就是把outer作為了裝飾器,等同于 old = outer(old)
def old():
? ? print('我是一個普通的函數')
old()? # old函數經過 outer裝飾器進行了裝飾,代碼和調用方法不變,但是函數的功能發送了改變
```
#### 2.裝飾器的應用:統計函數的執行時間
```python
# 裝飾器應用場景-統計函數執行時間
import time
# 定義一個統計函數執行時間的 裝飾器
def runtime(f):
? ? def inner():
? ? ? ? start = time.perf_counter()
? ? ? ? f()
? ? ? ? end =? time.perf_counter() - start
? ? ? ? print(f'函數的調用執行時間為:{end}')
? ? return inner
# 定義一個函數
@runtime
def func():
? ? for i in range(5):
? ? ? ? print(i,end=" ")
? ? ? ? time.sleep(1)
func()
```
#### 3.裝飾器嵌套語法
```python
# 1.定義裝飾器
# 外函數
def outer(func):
? ? #內函數
? ? def inner():
? ? ? ? print('找到妹子,成功拿到微信。。。3')
? ? ? ? func()? # 在內函數中調用外函數中的行參-函數
? ? ? ? print('約妹子,看一場午夜電影。。。4')
? ? # 在外函數中返回內函數
? ? return inner
# 2。在定義一個裝飾器
def kuozhan(f):
? ? def kzinner():
? ? ? ? print('擴展1')
? ? ? ? f()
? ? ? ? print('擴展2')
? ? return kzinner
# 3. 裝飾器的嵌套 先執行下面的,再執行上面的。
@kuozhan # 2。再使用上面的 kuozhan 裝飾器,裝飾 上一次返回的 inner 函數,又返回了 kzinner 函數
@outer? # 1。先使用離得近的 outer裝飾器 裝飾love函數,返回了一個 inner函數
def love():
? ? print('跟妹子暢談人生和理想。。。5')
love()
''' 結果和過程的解析
1 3 5 4 2
1 先使用離得近的 outer裝飾器 裝飾love函數,返回了一個 inner函數
2 再使用上面的 kuozhan 裝飾器,裝飾 上一次返回的 inner 函數,又返回了 kzinner 函數
最后在調用love函數的時候是怎么執行的
? ? love() == kzinner()
? ? ? ? ? ? ? ? ===>? 1
? ? ? ? ? ? ? ? ===>? inner()?
? ? ? ? ? ? ? ? ? ? ? ? ? ===> 3
? ? ? ? ? ? ? ? ? ? ? ? ? ===> love() ===> 5
? ? ? ? ? ? ? ? ? ? ? ? ? ===> 4
? ? ? ? ? ? ? ? ===>? 2
'''
```
#### 4.對帶有參數的函數進行裝飾
```python
# 定義裝飾器
def outer(func):
? ? # 如果裝飾器帶有參數的函數,需要在內函數中定義行參,并傳遞給調用的函數。因為調用原函數等于調用內函數
? ? def inner(var):
? ? ? ? print(f'找到{var}妹子,成功拿到微信。。')
? ? ? ? func(var)
? ? ? ? print(f'約{var}妹子,看一場午夜電影。。')
? ? return inner
# 有參數的函數
@outer
def love(name):
? ? print(f'跟{name}妹子暢談人生。。。')
love('思思') #love() ==> inner()? love('思思') ===> inner('思思')
```
#### 5.對多參數的函數進行裝飾
```python
# 裝飾帶有多參數的函數
def outer(func):
? ? def inner(who,name,*args,**kwargs):
? ? ? ? print('約到妹子,聊微信。。。')
? ? ? ? func(who,name,*args,**kwargs)
? ? ? ? print('天色一晚,怎么辦?')
? ? return inner
# 定義多參數的 函數
@outer
def love(who,name,*args,**kwargs):
? ? print(f'{who}跟{name}暢談人生。。。')
? ? print('完事去吃了好多美食',args)
? ? print('看了一場電影',kwargs)
love('三多','思思','火鍋','辣條','7塊錢的麻辣燙',mov='唐山大地震')
'''
love() ==> inner()
? ? love(...) ==> inner(...)
? ? ? ? inner(...) ==> love(...)
'''
```
#### 6.帶有參數的裝飾器
> 你會遇到帶有參數的裝飾器,例如Django框架中的 @login_required(login_url='/accounts/login/')
```python
# 如果你的裝飾器需要有參數,那么給當前的裝飾器套一個殼,用于接收裝飾器的參數
def kuozhan(var):
? ? def outer(func):
? ? ? ? def inner1():
? ? ? ? ? ? print('妹子給了你微信')
? ? ? ? ? ? func()
? ? ? ? def inner2():
? ? ? ? ? ? print('妹子給介紹了個大媽')
? ? ? ? ? ? func()
? ? ? ? # 裝飾器殼的參數,可以用于在函數內去做流程控制
? ? ? ? if var == 1:
? ? ? ? ? ? return inner1
? ? ? ? else:
? ? ? ? ? ? return inner2
? ? return outer
@kuozhan(2) # kuozhan(var) ==> outer() ==> outer(love) ==> inner()
def love():
? ? print('談談人生。。。')
love()
```
#### 7.用類裝飾器裝飾函數
```python
# 類裝飾器裝飾函數
class Outer():
? ? # 魔術方法:當把該類的對象當作函數調用時,自動觸發 obj()
? ? def __call__(self,func):
? ? ? ? self.func = func? # 把傳進來的函數作為對象的成員方法
? ? ? ? return self.inner # 返回一個函數
? ? # 在定義的需要返回的新方法中 去進行裝飾和處理
? ? def inner(self,who):
? ? ? ? print('拿到妹子的微信。。。')
? ? ? ? self.func(who)
? ? ? ? print('看一場午夜電影。。。')
@Outer()? # Outer() ==> obj? @obj==>obj(love) ==> __call__(love) ==> inner()
def love(who):
? ? print(f'{who}和妹子談談人生和理想。。。')
love('川哥') # inner('川哥')
print(love) # 此時的 love就是屬于Outer類這個對象中的inner方法
```
#### 8.用類方法裝飾函數
```python
# 用類方法裝飾函數
class Outer():
? ? def newinner(func):
? ? ? ? Outer.func = func? # 把傳遞進來的函數定義為類方法
? ? ? ? return Outer.inner # 同時返回一個新的類方法
? ? def inner():
? ? ? ? print('拿到妹子微信')
? ? ? ? Outer.func()
? ? ? ? print('看一場午夜電影')
@Outer.newinner? # Outer.newinner(love) ==> Outer.inner
def love():
? ? print('和妹子談談人生喝喝茶。。。')
love()? ? ? ? ? # love()? ==> Outer.inner()
```
到目前為止以上所以形式的裝飾器,包括 函數裝飾器,類裝飾器,類方法裝飾器,都有一個共同特點:都是在給函數去進行裝飾,增加功能。
---
### 用裝飾器裝飾類
> 還有一種裝飾器,是專門裝飾類的。也就是在類的定義的前面使用@裝飾器這種語法
> @裝飾器
> class Demo():
>? ? pass
> 裝飾器給函數進行裝飾,目的是不改變函數調用和代碼的情況下給原函數增加了新的功能。
> 裝飾器給類進行裝飾,目的是不改變類的定義和調用的情況下給類增加新的成員(屬性或方法)。
#### 9.用函數裝飾器裝飾類
```python
# 使用函數裝飾器,給類進行裝飾,增加新的屬性和方法
# 定義函數,接收一個類。返回修改后的類
def kuozhan(cls):
? ? def func2():
? ? ? ? print('我是在裝飾器中追加的新方法,func2')
? ? cls.func2 = func2 # 把剛才定義的方法賦值給 類
? ? cls.name = '我是在裝飾器中追加的新屬性 name'
? ? #返回時,把追加類新成員的 類 返回去
? ? return cls
@kuozhan? # kuozhan(Demo) ==> cls ==> Demo
class Demo():
? ? def func():
? ? ? ? print('我是Demo類中定義的func方法')
Demo.func() # 此時在調用的Demo類是通過裝飾器,更新過的Demo類
Demo.func2()
print(Demo.name)
```
#### 10.使用類裝飾器裝飾類
```python
class KuoZhan():
? ? def __call__(self, cls):
? ? ? ? # 把接收的類,賦值給當前對象,作為一個屬性
? ? ? ? self.cls = cls
? ? ? ? # 返回一個函數
? ? ? ? return self.newfunc
? ? def newfunc(self):
? ? ? ? self.cls.name = '我是在類裝飾器中追加的新屬性 name'
? ? ? ? self.cls.func2 = self.func2
? ? ? ? # 返回傳遞進來的類的實例化結果,obj
? ? ? ? return self.cls()
? ? def func2(self):
? ? ? ? print('我是在類裝飾器中追加的新方法 func2')
@KuoZhan()? # KuoZhan() ==> obj ==> @obj(Demo) ==> __call__(Demo) ==> newfunc
class Demo():
? ? def func(self):
? ? ? ? print('我是Demo類中定義的func方法')
obj = Demo()? # Demo() ==> newfunc() ==> obj
obj.func()
obj.func2()
print(obj.name)
# 思考: 此時的 obj這個對象,是哪個類的對象。Demo還是KuoZhan
print(obj) # 此時的obj依然是Demo類的實例化對象,只不過經過裝飾后,增加了新的屬性和方法
```
作業:如何用一個裝飾器,裝飾帶有返回值的函數