首頁 > 軟體

深入瞭解python裝飾器

2022-03-02 13:01:14

一、裝飾器

1.相關知識點

  • *args:負責將多餘的位置實參彙總,賦值給args
  • **kwargs:負責將多餘的關鍵字實參彙總,賦值給kwargs

名稱空間與作用域

函數物件:

  • 可以把函數當成引數傳入
  • 可以把函數當做返回值返回

函數的巢狀定義:在函數內定義函數

閉包函數:父函數的返回值為一個函數,被返回的函數呼叫了父函數的區域性變數,且該函數可以在父函數外部執行

裝飾器:

裝飾器:定義一個為其他函數新增功能的函數

為什麼要使用裝飾器?

  • 開放封閉原則:開放擴充套件功能但封閉原始碼的修改
  • 裝飾器就是在不修改裝飾物件原始碼以及呼叫方式的前提下,為裝飾物件新增新功能

裝飾器實現:

需求:不修改原始碼,計算程式碼執行時間

 原始碼:

import time
def func0(x):
    time.sleep(1)
    print(x)
func0(0)

# 方案一:實現了計算程式碼執行時間的功能,但修改了原始碼,不符合要求
def func1(x):
    start = time.time()
    time.sleep(1)
    print(x)
    end = time.time()
    print('方案一 執行時間:{}'.format(end - start))
func1(1)

# 方案二:滿足要求,但程式碼不具備重用性
start = time.time()
func0(2)
end = time.time()
print('方案二 執行時間:{}'.format(end - start))

# 方案三:將方案二封裝為函數,但修改了函數的呼叫方式,不符合要求
def wrapper():
    start = time.time()
    func0(3)
    end = time.time()
    print('方案三 執行時間:{}'.format(end - start))
wrapper()

# 方案四:不修改原函數的呼叫方式
def wrapper(x):
    start = time.time()
    func0(x)
    end = time.time()
    print('方案四 執行時間:{}'.format(end - start))
wrapper(4)

# 方案五:方案四引數被寫死,優化方案四
def wrapper(*args, **kwargs):
    start = time.time()
    func0(*args, **kwargs)
    end = time.time()
    print('方案五 執行時間:{}'.format(end - start))
wrapper(5)

# 方案六:方案五函數被寫死,優化方案五,但修改了原始碼的呼叫方式
def outter(a):
    def wrapper(*args, **kwargs):
        start = time.time()
        a(*args, **kwargs)
        end = time.time()
        print('方案六 執行時間:{}'.format(end - start))
    return wrapper
f = outter(func0)
f(6)

# 方案七:優化方案六(outter即為裝飾器,用於裝飾func0)
def outter(a):
    def wrapper(*args, **kwargs):
        start = time.time()
        a(*args, **kwargs)
        end = time.time()
        print('方案七 執行時間:{}'.format(end - start))
    return wrapper
func0 = outter(func0)    # 偷樑換柱,呼叫者無感知
func0(7)

執行結果:

0
1
方案一 執行時間:1.001857042312622
2
方案二 執行時間:1.0040733814239502
3
方案三 執行時間:1.0017154216766357
4
方案四 執行時間:1.007995367050171
5
方案五 執行時間:1.0145602226257324
6
方案六 執行時間:1.0046615600585938
7
方案七 執行時間:1.0094060897827148

2.語法糖

  • 使用語法糖,需要將裝飾器放到被裝飾物件前
# 不使用語法糖
import time
def func0(x):
    time.sleep(1)
    print(x)
# func0(0)

def outter(a):
    def wrapper(*args, **kwargs):
        start = time.time()
        a(*args, **kwargs)
        end = time.time()
        print('執行時間:{}'.format(end - start))
    return wrapper
func0 = outter(func0)
func0('hello')

# 使用語法糖
import time

def outter(a):
    def wrapper(*args, **kwargs):
        start = time.time()
        a(*args, **kwargs)
        end = time.time()
        print('執行時間:{}'.format(end - start))
    return wrapper

@outter # 語法糖,等價於該行func0 = outter(func0)
def func0(x):
    time.sleep(1)
    print(x)

func0('hello')
print(func0.__name__) 

執行結果:

hello
執行時間:1.0050427913665771
wrapper

3.裝飾器模板

 # 裝飾器模板
 def decorator_name(x):
     def wrapper(*args, **kwargs):
         # ...新新增的功能...
         # 下為呼叫原函數的格式
         x(*args, **kwargs)
     return wrapper

@decorator_name
 def func_name():
     pass

擴充套件:真正實現偷樑換柱,呼叫者無感知

  • 不使用裝飾器
 # 不使用裝飾器
import time


def func0(x):
    time.sleep(1)
    print(x)

func0('hello')
print(func0.__name__) # 檢視函數名 
print(help(func0)) # 檢視幫助資訊(主要為註釋內容)

執行結果:

hello
func0
Help on function func0 in module main:

func0(x)
這是函數

None

使用裝飾器:

import time

def outter(a):
    def wrapper(*args, **kwargs):
        '''這是裝飾器'''
        start = time.time()
        a(*args, **kwargs)
        end = time.time()
        print('執行時間:{}'.format(end - start))
    return wrapper

@outter # func0 = outter(func0)
def func0(x):
    '''這是函數'''
    time.sleep(1)
    print(x)

func0('hello')
print(func0.__name__)
print(help(func0))

執行結果:

hello
執行時間:1.011878490447998
wrapper
Help on function wrapper in module main:

wrapper(*args, **kwargs)
這是裝飾器

None

嘔吼,露餡了

  • 解決方法,將原函數的屬性和方法,賦值給裝飾器內的wrapper
import time

def outter(a):
    def wrapper(*args, **kwargs):
        '''這是裝飾器'''
        start = time.time()
        a(*args, **kwargs)
        end = time.time()
        print('執行時間:{}'.format(end - start))
    wrapper.__name__ = a.__name__
    wrapper.__doc__ = a.__doc__
    return wrapper

@outter # func0 = outter(func0)
def func0(x):
    '''這是函數'''
    time.sleep(1)
    print(x)

func0('hello')
print(func0.__name__)
print(help(func0))

執行結果:

hello
執行時間:1.0030155181884766
func0
Help on function func0 in module main:

func0(*args, **kwargs)
這是函數

None

但是,函數有很多屬性和方法,一個一個手動修改過於麻煩,甚至可能會遺漏,但python也提供瞭解決方法

import time
from functools import wraps

def outter(a):
    @wraps(a)
    def wrapper(*args, **kwargs):
        '''這是裝飾器'''
        start = time.time()
        a(*args, **kwargs)
        end = time.time()
        print('執行時間:{}'.format(end - start))
    # wrapper.__name__ = a.__name__
    # wrapper.__doc__ = a.__doc__
    return wrapper

@outter # func0 = outter(func0)
def func0(x):
    '''這是函數'''
    time.sleep(1)
    print(x)

func0('hello')
print(func0.__name__)
print(help(func0))

執行結果:

hello
執行時間:1.0114128589630127
func0
Help on function func0 in module main:

func0(x)
這是函數

None

4.有參裝飾器

  • 裝飾器內需要傳入引數,但是由於語法糖的限制,裝飾器只能有一個引數,並且該引數僅用來接收被裝飾物件的記憶體地址,如何傳入其他引數?
  • 解決思路:將裝飾器做成閉包函數
def outter(db_type):
    # 裝飾器auth
    def auth(x):
        def wrapper(*args, **kwargs):
            if db_type == 'file':
                name = input('請輸入姓名:')
                passwd = input('請輸入密碼:')
                if name == 'zhangsan' and passwd == '123':
                    x(*args, **kwargs)
                    print('基於檔案認證')
                else:
                    print('使用者名稱或密碼錯誤,認證失敗')
            elif db_type == 'mysql':
                x(*args, **kwargs)
                print('基於資料庫認證')
            else:
                print('未知認證方式,不支援')
        return wrapper
    return auth

# 函數
auth = outter(db_type='file')
@auth
def file():
    print('hello')


auth = outter(db_type='mysql')
@auth
def mysql():
    print('world')

msg = input('請選擇認證方式(1=file|2=mysql):').strip()
if msg =='1':
    file()
elif msg == '2':
    mysql()
else:
    print('不支援')
  • 將傳入的引數寫入語法糖
def outter(db_type):
    # 裝飾器auth
    def auth(x):
        def wrapper(*args, **kwargs):
            if db_type == 'file':
                name = input('請輸入姓名:')
                passwd = input('請輸入密碼:')
                if name == 'zhangsan' and passwd == '123':
                    x(*args, **kwargs)
                    print('基於檔案認證')
                else:
                    print('使用者名稱或密碼錯誤,認證失敗')
            elif db_type == 'mysql':
                x(*args, **kwargs)
                print('基於資料庫認證')
            else:
                print('未知認證方式,不支援')
        return wrapper
    return auth

# 函數
# auth = outter(db_type='file')
# @auth
@outter(db_type='file')
def file():
    print('hello')

# auth = outter(db_type='mysql')
# @auth
@outter(db_type='mysql')
def mysql():
    print('world')

msg = input('請選擇認證方式(1=file|2=mysql):').strip()
if msg =='1':
    file()
elif msg == '2':
    mysql()
else:
    print('不支援')
  • 模板
# 有參裝飾器模板
def out_decorator_name(x,y,z):
    def decorator_name(a):
        def wrapper(*args, **kwargs):
            # ...新新增的功能...
            # 下為呼叫原函數的格式
            a(*args, **kwargs)
        return wrapper
    return decorator_name

@out_decorator_name(x,y,z=1)
def func_name():
     pass

到此這篇關於深入瞭解python裝飾器的文章就介紹到這了,更多相關python裝飾器內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com