1. 程式人生 > >你真的會用PYTHON的裝飾器了嗎?(老鐵)

你真的會用PYTHON的裝飾器了嗎?(老鐵)

本文結構:

  • 無參裝飾器的一般形式

               a、 解決原生函式有參問題
               b、解決原生函式有返回值問題

  • 無參裝飾器的模型總結
  • 無參裝飾器的具體應用例項
  • 有參裝飾器
  • 有參裝飾器的具體應用例項

 對於大部分學Python的人來說,裝飾器可能是遇到的第一個坎,裝飾器到底是什麼,到底應該怎麼用?本篇部落格將進行徹底的講解。

裝飾器的概念:

1、裝飾器就是為了在不修改被裝飾物件的原始碼以及呼叫方式的前提下,為其新增新的功能;

2、裝飾器本身可以是任何可呼叫的物件,被裝飾的物件也可以是任意可呼叫的物件;

簡單來說裝飾器就是修改別人的工具,其中修飾指的是新增功能,工具指的是函式。


裝飾器的語法:

假設被裝飾的函式是index,請寫出@timer的含義:

index = timer(index)


場景:假如我現在有3個函式,如下所示:

def index():
    print('歡迎來到python世界')

def home():
    print('歡迎來到scala世界')

def edit():
    print('歡迎來到Java世界')

index()
home()
edit()

現在的需求:求出每一個函式的執行時間:

渣渣aa的做法:

import time

def index():
    start_time = time.time()
    print('歡迎來到python世界')
    end_time = time.time()
    print('run time is %s'%(end_time-start_time))

def home():
    start_time = time.time()
    print('歡迎來到scala世界')
    end_time = time.time()
    print('run time is %s'%(end_time-start_time))

def edit():
    start_time = time.time()
    print('歡迎來到Java世界')
    end_time = time.time()
    print('run time is %s'%(end_time-start_time))


index()
home()
edit()

 渣渣bb的做法:

def decorator(func):
    start_time = time.time()
    func()
    end_time = time.time()
    print('run time is %s'%(end_time-start_time))

def index():
    print('歡迎來到python世界')

def home():
    print('歡迎來到scala世界')

def edit():
    print('歡迎來到Java世界')

decorator(index)
decorator(home)
decorator(edit)

其中渣渣a的問題:修改了原始碼;渣渣b的問題:修改了函式正常的呼叫方式

問題產生了:我們如何在不修改被裝飾物件的原始碼以及呼叫方式的前提下為函式增加新的功能呢?這個時候我們的裝飾器就上場了。

裝飾器的解決方案:

import time

def decorator(func):
    def wrapper():
        start_time = time.time()
        func()
        end_time = time.time()
        print('run time is %s' % (end_time - start_time))
    return wrapper

@decorator
def index():
    print('歡迎來到python世界')

@decorator
def home():
    print('歡迎來到scala世界')

@decorator
def edit():
    print('歡迎來到Java世界')

index()
home()
edit()  

執行結果:

View Code

 對於上面的程式碼,第一眼看上去是不是感覺很蒙圈,其實那是因為我中間省略了一個重要的步驟:

#!/usr/bin/python
# -*- coding:utf-8 -*-

import time

def decorator(func):
    def wrapper():
        start_time = time.time()
        func()
        end_time = time.time()
        print('run time is %s' % (end_time - start_time))
    return wrapper

def index():
    print('歡迎來到python世界')
index = decorator(index)
print(index,'   ',index.__name__)

def home():
    print('歡迎來到scala世界')
home = decorator(home)

def edit():
    print('歡迎來到Java世界')
edit = decorator(edit)


index()
home()
edit()

隨後在上一張圖片:當執行完  index = decorator(index) 之後:返回的wrapper實際上是一個指標變數,所以index和wrapper將指向同一塊記憶體空間,也就是說index已經不是我們之前的index了,而是悄悄的變成了閉包函式wrapper,wrapper在控制著index。

我想你也許發現哪裡發生變化了,即index = decorato(index)等價於@decorator,對的,這就是裝飾器的本質:Python的一種語法糖而已,裝飾器就是閉包函式的一種實現。

上面我們使用的是簡單的無參裝飾器,但是對於上面的程式實際上還是有兩個缺陷的:

假如3個原生函式是這樣的:

View Code

如果我們還按照上面的方式去執行程式碼,將會丟擲錯誤,因為當執行到func()的時候,實際上執行的是原始的index函式,但是我們在呼叫的時候並沒有傳入引數,所以:解決bug1:原生函式帶有引數

import time

def decorator(func):
    def wrapper(*args,**kwargs):   #wrapper('小泡芙')
        start_time = time.time()
        func(*args,**kwargs)   #執行到這裡的時候會發現少了一個引數,因為原始的index函式必須傳入一個引數
        end_time = time.time()
        print('run time is %s' % (end_time - start_time))
    return wrapper

@decorator
def index(name):
    print('歡迎來到python世界')

@decorator
def home():
    print('歡迎來到scala世界')

@decorator
def edit():
    print('歡迎來到Java世界')

index('小泡芙')
home()
edit()

 但是接下來問題又來了,如果原生函式帶有返回值我們怎麼獲取呢?

@decorator
def index(name):
    print('歡迎來到python世界')
    return 'Hello World'

@decorator
def home():
    print('歡迎來到scala世界')

@decorator
def edit():
    print('歡迎來到Java世界')

print(index('小泡芙'))
home()
edit()

如果我們還按照以前的方式的話,你會發現:返回值將是None,為什麼呢?因為wrapper函式的返回值就是None。

解決bug2:原生函式帶有返回值

#!/usr/bin/python
# -*- coding:utf-8 -*-

import time

def decorator(func):
    def wrapper(*args,**kwargs):   #wrapper('小泡芙')
        start_time = time.time()
        res = func(*args,**kwargs)   #執行到這裡的時候會發現少了一個引數,因為原始的index函式必須傳入一個引數
        end_time = time.time()
        print('run time is %s' % (end_time - start_time))
        return res 
    return wrapper

@decorator
def index(name):
    print('歡迎來到python世界')
    return 'Hello World'

@decorator
def home():
    print('歡迎來到scala世界')

@decorator
def edit():
    print('歡迎來到Java世界')

print(index('小泡芙'))
home()
edit()

 執行結果:

歡迎來到python世界
run time is 0.0
Hello World
歡迎來到scala世界
run time is 0.0
歡迎來到Java世界
run time is 0.0

Process finished with exit code 0

 到這裡我們將引出常用無參裝飾器的模型:

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

 接下來我們舉一個具體的例項場景來說明裝飾器的具體應用:要求函式在執行真正的程式碼之前,先實現一段認證功能,只有認證通過了,

才可以執行真正的功能。(該場景實際上是後期Django的cookie和session的應用)

#!/usr/bin/python
# -*- coding:utf-8 -*-

import time

def decorator(func):
    def wrapper(*args,**kwargs):
        name = input('請輸入使用者名稱:')
        pwd = input('請輸入密碼:')
        if name == 'eric' and pwd == '123456':
            print('\033[42m登陸成功....\033[0m')
            res = func(*args,**kwargs)
            return res
        else:
            print('\033[42m登陸失敗,您的使用者名稱或者密碼輸入有誤..\033[0m')
    return wrapper

@decorator
def index():
    print('歡迎來到登陸頁面')

@decorator
def home():
    print('歡迎來到使用者資訊介面')

@decorator
def edit():
    print('歡迎來到編輯介面')


index()
home()
edit()

 執行結果:

請輸入使用者名稱:eric
請輸入密碼:123456
登陸成功....
歡迎來到登陸頁面
請輸入使用者名稱:Jack
請輸入密碼:12346
登陸失敗,您的使用者名稱或者密碼輸入有誤..
請輸入使用者名稱:Angela
請輸入密碼:123
登陸失敗,您的使用者名稱或者密碼輸入有誤..

但是上面的這個程式實際上還是存在問題,什麼問題呢?這個問題就好比我已經登陸了京東的網頁頁面,但是後續無論我訪問什麼頁面都需要再次登入一下登陸介面,這個問題確實有點扯。

解決方法:這個問題的真實場景是通過cookie或者session來記錄使用者登入狀態的,但是在這裡我們只能暫時通過全域性變數來模擬這種效果了。

#!/usr/bin/python
# -*- coding:utf-8 -*-

import time

user_info = {'user':None,'status':False}

def decorator(func):
    def wrapper(*args,**kwargs):
        if user_info['user'] and user_info['status']:
            res = func(*args, **kwargs)
            return res
        else:
            name = input('請輸入使用者名稱:')
            pwd = input('請輸入密碼:')
            if name == 'eric' and pwd == '123456':
                print('\033[42m登陸成功....\033[0m')
                # 使用者一旦登陸成功,我們就將登陸成功的資訊記錄下來
                user_info['user'] = 'eric'
                user_info['status'] = True

                res = func(*args,**kwargs)
                return res
            else:
                print('\033[42m登陸失敗,您的使用者名稱或者密碼輸入有誤..\033[0m')
    return wrapper

@decorator
def index():
    print('歡迎來到登陸頁面')

@decorator
def home():
    print('歡迎來到使用者資訊介面')

@decorator
def edit():
    print('歡迎來到編輯介面')


index()
home()
edit()

接下來執行結果就正常了:

請輸入使用者名稱:eric
請輸入密碼:123456
登陸成功....
歡迎來到登陸頁面
歡迎來到使用者資訊介面
歡迎來到編輯介面

Process finished with exit code 0

 

上面的無參裝飾器我們講完了,接下來我們來談論有參裝飾器的概念:

有參裝飾器的模型:

def outer(driver='file'):
    def decorator(func):
        def wrapper(*args, **kwargs):
            res = func(*args, **kwargs)
            return res
        return wrapper
    return decorator

其實有參裝飾器也沒有想象中的那麼難,本質上也是閉包函式和無參裝飾器的擴充套件。

以上面的場景為例:使用者認證的方式包括很多,如檔案認證、資料庫認證等等,如果利用有參裝飾器進行實現呢?

程式碼示例:

#!/usr/bin/python
# -*- coding:utf-8 -*-

import time

user_info = {'user':None,'status':False}

def outer(auth='file'):
    def decorator(func):
        def wrapper(*args,**kwargs):
            if auth == 'file':
                print('file的驗證方式')
                if user_info['user'] and user_info['status']:
                    res = func(*args, **kwargs)
                    return res
                else:
                    name = input('請輸入使用者名稱:')
                    pwd = input('請輸入密碼:')
                    if name == 'eric' and pwd == '123456':
                        print('\033[42m登陸成功....\033[0m')
                        # 使用者一旦登陸成功,我們就將登陸成功的資訊記錄下來
                        user_info['user'] = 'eric'
                        user_info['status'] = True

                        res = func(*args,**kwargs)
                        return res
                    else:
                        print('\033[42m登陸失敗,您的使用者名稱或者密碼輸入有誤..\033[0m')
            elif auth == 'dba':
                print('dba的驗證方式')
                if user_info['user'] and user_info['status']:
                    res = func(*args, **kwargs)
                    return res
                else:
                    name = input('請輸入使用者名稱:')
                    pwd = input('請輸入密碼:')
                    if name == 'eric' and pwd == '123456':
                        print('\033[42m登陸成功....\033[0m')
                        # 使用者一旦登陸成功,我們就將登陸成功的資訊記錄下來
                        user_info['user'] = 'eric'
                        user_info['status'] = True

                        res = func(*args,**kwargs)
                        return res
                    else:
                        print('\033[42m登陸失敗,您的使用者名稱或者密碼輸入有誤..\033[0m')
            else:
                print('其餘的驗證方式')
                if user_info['user'] and user_info['status']:
                    res = func(*args, **kwargs)
                    return res
                else:
                    name = input('請輸入使用者名稱:')
                    pwd = input('請輸入密碼:')
                    if name == 'eric' and pwd == '123456':
                        print('\033[42m登陸成功....\033[0m')
                        # 使用者一旦登陸成功,我們就將登陸成功的資訊記錄下來
                        user_info['user'] = 'eric'
                        user_info['status'] = True

                        res = func(*args,**kwargs)
                        return res
                    else:
                        print('\033[42m登陸失敗,您的使用者名稱或者密碼輸入有誤..\033[0m')
        return wrapper
    return decorator



def index():
    print('歡迎來到登陸頁面')
decorator = outer(auth='file')
index = decorator(index)

#index本質上還是wrapper
print(index,index.__name__)


def home():
    print('歡迎來到使用者資訊介面')
decorator = outer(auth='else')
home = decorator(home)

#home本質上還是wrapper
print(home,home.__name__)


def edit():
    print('歡迎來到編輯介面')
decorator = outer(auth='dba')
edit = decorator(edit)

#edit本質上還是wrapper
print(edit,edit.__name__)


index()
home()
edit()

我們寫成裝飾器的形式:

#!/usr/bin/python
# -*- coding:utf-8 -*-

import time

user_info = {'user':None,'status':False}

def outer(auth='file'):
    def decorator(func):
        def wrapper(*args,**kwargs):
            if auth == 'file':
                print('file的驗證方式')
                if user_info['user'] and user_info['status']:
                    res = func(*args, **kwargs)
                    return res
                else:
                    name = input('請輸入使用者名稱:')
                    pwd = input('請輸入密碼:')
                    if name == 'eric' and pwd == '123456':
                        print('\033[42m登陸成功....\033[0m')
                        # 使用者一旦登陸成功,我們就將登陸成功的資訊記錄下來
                        user_info['user'] = 'eric'
                        user_info['status'] = True

                        res = func(*args,**kwargs)
                        return res
                    else:
                        print('\033[42m登陸失敗,您的使用者名稱或者密碼輸入有誤..\033[0m')
            elif auth == 'dba':
                print('dba的驗證方式')
                if user_info['user'] and user_info['status']:
                    res = func(*args, **kwargs)
                    return res
                else:
                    name = input('請輸入使用者名稱:')
                    pwd = input('請輸入密碼:')
                    if name == 'eric' and pwd == '123456':
                        print('\033[42m登陸成功....\033[0m')
                        # 使用者一旦登陸成功,我們就將登陸成功的資訊記錄下來
                        user_info['user'] = 'eric'
                        user_info['status'] = True

                        res = func(*args,**kwargs)
                        return res
                    else:
                        print('\033[42m登陸失敗,您的使用者名稱或者密碼輸入有誤..\033[0m')
            else:
                print('其餘的驗證方式')
                if user_info['user'] and user_info['status']:
                    res = func(*args, **kwargs)
                    return res
                else:
                    name = input('請輸入使用者名稱:')
                    pwd = input('請輸入密碼:')
                    if name == 'eric' and pwd == '123456':
                        print('\033[42m登陸成功....\033[0m')
                        # 使用者一旦登陸成功,我們就將登陸成功的資訊記錄下來
                        user_info['user'] = 'eric'
                        user_info['status'] = True

                        res = func(*args,**kwargs)
                        return res
                    else:
                        print('\033[42m登陸失敗,您的使用者名稱或者密碼輸入有誤..\033[0m')
        return wrapper
    return decorator



@outer(auth='file')
def index():
    print('歡迎來到登陸頁面')


@outer(auth='dba')
def home():
    print('歡迎來到使用者資訊介面')


@outer(auth='else')
def edit():
    print('歡迎來到編輯介面')



index()
home()
edit()

對於有參裝飾器,我們注意兩點就夠了:

@outer(auth='file') ===> @decorator  ==> index=decorator(index),也就是說,有參裝飾器又給內部提供了一個引數。

實際上就是在最開始多了一個步驟,後面的步驟和我們上面是一模一樣的。

OK,如有問題,歡迎留言指正。