裝飾器


一、無參裝飾器

1.1 什么是裝飾器?

器指的是工具,而程序中的函數就是具備某一功能的工具,所以裝飾器指的是為被裝飾器對象添加額外功能。因此定義裝飾器就是定義一個函數,只不過該函數的功能是用來為其他函數添加額外的功能。

需要注意的是:

  • 裝飾器本身其實是可以任意可調用的對象
  • 被裝飾的對象也可以是任意可調用的對象

1.2 為什么要用裝飾器?

如果我們已經上線了一個項目,我們需要修改某一個方法,但是我們不想修改方法的使用方法,這個時候可以使用裝飾器。因為軟件的維護應該遵循開放封閉原則,即軟件一旦上線運行后,軟件的維護對修改源代碼是封閉的,對擴展功能指的是開放的。

裝飾器的實現必須遵循兩大原則:

  1. 不修改被裝飾對象的源代碼
  2. 不修改被裝飾對象的調用方式

裝飾器其實就是在遵循以上兩個原則的前提下為被裝飾對象添加新功能。

48裝飾器-bug.jpg?x-oss-process=style/watermark

1.3 怎么用裝飾器?

改變源代碼

import time


def index():
    start = time.time()
    print('welcome to index')
    time.sleep(1)
    end = time.time()
    print(F"index run time is {start-end}")


index()
welcome to index
index run time is -1.0008180141448975

編寫重復代碼

import time


def index():
    print('welcome to index')
    time.sleep(1)


def f2():
    print('welcome to index')
    time.sleep(1)


start = time.time()
index()
end = time.time()
print(F"index run time is {start-end}")

start = time.time()
f2()
end = time.time()
print(F"f2 run time is {start-end}")
welcome to index
index run time is -1.0046868324279785
welcome to index
f2 run time is -1.000690221786499

第一種傳參方式:改變調用方式

import time


def index():
    print('welcome to index')
    time.sleep(1)


def time_count(func):
    start = time.time()
    func()
    end = time.time()
    print(f"{func} time is {start-end}")


time_count(index)
welcome to index
<function index at 0x102977378> time is -1.000748872756958

第二種傳參方式:包給函數-外包

import time


def index():
    print('welcome to index')
    time.sleep(1)


def time_count(func):
    # func = 最原始的index
    def wrapper():
        start = time.time()
        func()
        end = time.time()
        print(f"{func} time is {start-end}")
    return wrapper

# f = time_count(index)
# f()


index = time_count(index)  # index為被裝飾函數的內存地址,即index = wrapper
index()  # wrapper()
welcome to index
<function index at 0x102977730> time is -1.0038220882415771

1.4 完善裝飾器

上述的裝飾器,最后調用index()的時候,其實是在調用wrapper(),因此如果原始的index()有返回值的時候,wrapper()函數的返回值應該和index()的返回值相同,也就是說,我們需要同步原始的index()和wrapper()方法的返回值。

import time


def index():
    print('welcome to index')
    time.sleep(1)

    return 123


def time_count(func):
    # func = 最原始的index
    def wrapper():
        start = time.time()
        res = func()
        end = time.time()
        print(f"{func} time is {start-end}")

        return res
    return wrapper


index = time_count(index)
res = index()
print(f"res: {res}")
welcome to index
<function index at 0x102977620> time is -1.0050289630889893
res: 123

如果原始的index()方法需要傳參,那么我們之前的裝飾器是無法實現該功能的,由於有wrapper()=index(),所以給wrapper()方法傳參即可。

import time


def index():
    print('welcome to index')
    time.sleep(1)

    return 123


def home(name):
    print(f"welcome {name} to home page")
    time.sleep(1)

    return name


def time_count(func):
    # func = 最原始的index
    def wrapper(*args, **kwargs):
        start = time.time()
        res = func(*args, **kwargs)
        end = time.time()
        print(f"{func} time is {start-end}")

        return res
    return wrapper


home = time_count(home)

res = home('egon')
print(f"res: {res}")
welcome egon to home page
<function home at 0x102977378> time is -1.0039079189300537
res: egon

1.5 裝飾器語法糖

在被裝飾函數正上方,並且是單獨一行寫上@裝飾器名

48裝飾器-糖.jpg?x-oss-process=style/watermark

import time


def time_count(func):
    # func = 最原始的index
    def wrapper(*args, **kwargs):
        start = time.time()
        res = func(*args, **kwargs)
        end = time.time()
        print(f"{func} time is {start-end}")

        return res
    return wrapper


@time_count  # home = time_count(home)
def home(name):
    print(f"welcome {name} to home page")
    time.sleep(1)

    return name


@time_count  # index = time_count(index)
def index():
    print('welcome to index')
    time.sleep(1)

    return 123


res = home('egon')
print(f"res: {res}")
welcome egon to home page
<function home at 0x102977620> time is -1.0005171298980713
res: egon

1.6 裝飾器模板

def deco(func):
    def wrapper(*args,**kwargs):
        res = func(*args,**kwargs)
        return res
    return wrapper

二、有參裝飾器

無參裝飾器只套了兩層,本節將講一個套三層的裝飾器——有參裝飾器,但現在我們先實現一個用戶登錄注冊的裝飾器。

import time

current_user = {'username': None}


def login(func):
    # func = 最原始的index
    def wrapper(*args, **kwargs):
        if current_user['username']:
            res = func(*args, **kwargs)

            return res

        user = input('username: ').strip()
        pwd = input('password: ').strip()

        if user == 'nick' and pwd == '123':
            print('login successful')
            current_uesr['usre'] = user
            res = func(*args, **kwargs)

            return res
        else:
            print('user or password error')

    return wrapper


@login
def home(name):
    print(f"welcome {name} to home page")
    time.sleep(1)

    return name


@login
def index():
    print('welcome to index')
    time.sleep(1)

    return 123


res = index()
username: nick
password: 123
login successful
welcome to index

對於上面的登錄注冊,我們把用戶登錄成功的信息寫入內存當中。但是在工業上,用戶信息可以存在文本中、mysql中、mongodb當中,但是我們只讓用戶信息來自於file的用戶可以認證。因此我們可以改寫上述的裝飾器。

48裝飾器-跑路.jpg?x-oss-process=style/watermark

import time

current_user = {'username': None}


def login(func):
    # func = 最原始的index
    def wrapper(*args, **kwargs):

        if current_user['username']:
            res = func(*args, **kwargs)

            return res

        user = input('username: ').strip()
        pwd = input('password: ').strip()
        
        engine = 'file'

        if engine == 'file':
            print('base of file')
            if user == 'nick' and pwd == '123':
                print('login successful')
                current_uesr['usre'] = user
                res = func(*args, **kwargs)

                return res
            else:
                print('user or password error')
        elif engine == 'mysql':
            print('base of mysql')
        elif engine == 'mongodb':
            print('base of mongodb')
        else:
            print('default')

    return wrapper


@login
def home(name):
    print(f"welcome {name} to home page")
    time.sleep(1)


@login
def index():
    print('welcome to index')
    time.sleep(1)


res = index()
username: nick
password: 123
base of file
login successful
welcome to index

2.1 三層閉包

我們首先看看閉包,包三層怎么運用。

48裝飾器-樓房三層.jpg?x-oss-process=style/watermark

def f1(y):

    def f2():
        x = 1

        def f3():
            print(f"x: {x}")
            print(f"y: {y}")
        return f3
    return f2


f2 = f1(2)
f3 = f2()
f3()
x: 1
y: 2

現在需求改了,我們需要判斷用戶動態的獲取用戶密碼的方式,如果是file類型的,我們則讓用戶進行認證。因此我們可以使用有參裝飾器。

import time

current_uesr = {'username': None}


def auth(engine='file'):

    def login(func):
        # func = 最原始的index
        def wrapper(*args, **kwargs):

            if current_user['username']:
                res = func(*args, **kwargs)

                return res

            user = input('username: ').strip()
            pwd = input('password: ').strip()

            if engine == 'file':
                print('base of file')
                if user == 'nick' and pwd == '123':
                    print('login successful')
                    current_uesr['usre'] = user
                    res = func(*args, **kwargs)

                    return res
                else:
                    print('user or password error')
            elif engine == 'mysql':
                print('base of mysql, please base of file')
            elif engine == 'mongodb':
                print('base of mongodb, please base of file')
            else:
                print('please base of file')

        return wrapper

    return login


@auth(engine='mysql')
def home(name):
    print(f"welcome {name} to home page")
    time.sleep(1)


@auth(engine='file')
def index():
    print('welcome to index')
    time.sleep(1)


res = index()
username: nick
password: 123
base of file
login successful
welcome to index

由於兩層的裝飾器,參數必須得固定位func,但是三層的裝飾器解除了這個限制。我們不僅僅可以使用上述單個參數的三層裝飾器,多個參數的只需要在三層裝飾器中多加入幾個參數即可。也就是說裝飾器三層即可,多加一層反倒無用。

48裝飾器-終了.jpg?x-oss-process=style/watermark


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM