裝飾器

1、什麼是裝飾器mysql

器:工具sql

裝飾:爲被裝飾對象添加新功能數據庫

裝飾器自己能夠是任意可調用的對象,即函數閉包

被裝飾的對象也能夠是任意可調用的對象,也是函數app

目標:寫一個函數來爲另一個函數添加新功能ide

2、爲什麼要用裝飾器函數

開放封閉原則:軟件一旦上線就應該對修改封閉,對擴展開放工具

  對修改封閉:spa

    一、不能修改功能的源代碼code

    二、也不能修改功能的調用方式

  對擴展開放:

    能夠爲原有的功能添加新的功能

裝飾器就是要在不修改功能源代碼以及調用方式的前提下爲原功能添加額外新的功能

3、使用裝飾器

# 第一步
import time def index(): print('歡迎來到index頁面') time.sleep(3) start = time.time() index() stop = time.time() print("執行時間是: %s" %(stop - start)) # 若是想屢次計算程序運行時間,每次都要寫這幾步相減操做去計算

#==========================================================================================

# 第二步
import time def index(): print('歡迎來到index頁面') time.sleep(3) # 如今增長函數,能夠經過函數實現屢次計算
def wrapper(): start = time.time() index() stop = time.time() print("執行時間是: %s" %(stop - start)) wrapper() # 但每次只能計算index, 功能寫死了,且修改了程序的執行方式

#==========================================================================================

# 第三步
import time def index(): print('歡迎來到index頁面') time.sleep(3) func = index    # func = index最原始的內存地址
def wrapper(): start = time.time() func() stop = time.time() print("執行時間是: %s" %(stop - start)) wrapper() # 能夠計算其餘函數了, 但仍是修改了程序的執行方式

#==========================================================================================

# 第四步
def index(): print('歡迎來到index頁面') time.sleep(3) # 加上閉包函數,實現傳參
def outter(): func = index    # func = index最原始的內存地址
    def wrapper(): start = time.time() func() stop = time.time() print("執行時間是: %s" %(stop - start)) return wrapper outter() # 執行outter(), 拿到wrapper的內存地址, 能夠將index做爲outter的參數

#==========================================================================================

# 第五步

import time def index(): print('歡迎來到index頁面') time.sleep(3) # 將func做爲outter的參數,用於實現傳入不一樣的參數,計算不一樣程序的運行時間
def outter(func):   # func = index最原始的內存地址
    def wrapper(): print("hhhhh") start = time.time() func() stop = time.time() print("執行時間是: %s" %(stop - start)) return wrapper # 將index做爲outter的參數, 拿到wrapper的內存地址
index = outter(index) # 賦值給新的index, 加括號執行
index() # 實現了一個簡單裝飾器的功能
 如今想給 index 一個返回值, 要想獲得這個返回值,只須要將 index函數的執行結果賦給一個變量,輸出這個變量便可 import time def index(): print('歡迎來到index頁面') time.sleep(3) return 123

def outter(func): def wrapper(): start = time.time() res = func() stop = time.time() print("執行時間是: %s" %(stop - start)) return res return wrapper index = outter(index) res = index() print(res)
一個簡單的裝飾器

可是這樣只能輸出 index 函數的返回值,功能又寫死了,假如其餘函數有返回值呢,但你又不知道返回值是什麼,因而須要有進一步的操做

import time def index(): print('歡迎來到index頁面') time.sleep(3) return 123

def home(name): print('歡迎 %s 來到home頁面' %name) time.sleep(1) def outter(func): # 給wrapper加上參數,只用來接收被裝飾函數的參數, 原封不動的轉接給最原始的函數
    def wrapper(*args, **kwargs): start = time.time() res = func(*args, **kwargs) stop = time.time() print("執行時間是: %s" %(stop - start)) return res return wrapper index = outter(index) home = outter(home) home('qiuxi') index()
改進一

每次調用 outter 函數再傳參,再將 outter 的執行結果賦給新的變量,顯得很是麻煩,因而能夠有簡化操做

import time def outter(func): def wrapper(*args, **kwargs): start = time.time() res = func(*args, **kwargs) stop = time.time() print("執行時間是: %s" %(stop - start)) return res return wrapper # 一執行這行代碼, 就會調用outter(),將下方的函數名做爲參數傳入outter # 將返回的結果從新賦值給函數名,即 index = outter(index)
@outter def index(): print('歡迎來到index頁面') time.sleep(3) return 123

# 這裏和上面是相同的操做 # home = outter(home)
@outter def home(name): print('歡迎 %s 來到home頁面' %name) time.sleep(1) home('qiuxi') index()
改進二

由於是計算執行時間的程序,因此我給裝飾器換個名字,這一步沒什麼操做

import time def timmer(func): def wrapper(*args, **kwargs): start = time.time() res = func(*args, **kwargs) stop = time.time() print("執行時間是: %s" %(stop - start)) return res return wrapper # 一執行這行, 就會調用timmer() # timmer將返回的結果從新賦值給函數名 # 即 index = timmer(index)
@timmer def index(): print('歡迎來到index頁面') time.sleep(3) return 123

# home = timmer(home)
@timmer def home(name): print('歡迎 %s 來到home頁面' %name) time.sleep(1) home('qiuxi') index()
改進三

寫函數的時候,咱們要爲函數添加註釋功能,是在定義函數的下方用三引號寫出來,以便於代碼的閱讀

import time def timmer(func): def wrapper(*args, **kwargs): start = time.time() res = func(*args, **kwargs) stop = time.time() print("執行時間是: %s" %(stop - start)) return res return wrapper # @timmer
def index(): '''這是index功能'''
    print('歡迎來到index頁面') time.sleep(3) return 123

# @timmer
def home(name): '''這是home功能'''
    print('歡迎 %s 來到home頁面' %name) time.sleep(1) # home('qiuxi') # index()

# 如今我將裝飾器這行註釋掉 # 不用裝飾器打印的是函數的註釋信息 # 如今的需求是加上裝飾器還應該是函數的註釋信息 # 但這裏加上裝飾器打印的倒是wrapper的註釋信息 # 是由於加上裝飾器後,index與home指向的都是wrapper
print(help(index)) print(help(home))
改進四

加了裝飾器後,這裏經過index看到wrapper的註釋信息,但如今仍是想看到本身函數的註釋信息,能夠到wrapper裏面把本身的 __doc__ 這個功能賦給 wrapper,還有一個功能是 __name__,這個功能是查看對應的函數名,不加裝飾器對應的是本身的函數名,加了裝飾器對應的是 wrapper 的函數名,如今需求是加了裝飾器也是對應本身的函數名,能夠到 wrapper 裏面把這個功能賦給wrapper

import time def timmer(func): def wrapper(*args, **kwargs): start = time.time() res = func(*args, **kwargs) stop = time.time() print("執行時間是: %s" %(stop - start)) return res # 把傳入的函數的註釋信息賦值給wrapper的註釋信息
    wrapper.__doc__ = func.__doc__
    # 把傳入函數的函數名賦給wrapper
    wrapper.__name__ = func.__name__
    return wrapper @timmer def index(): '''這是index功能'''
    print('歡迎來到index頁面') time.sleep(3) return 123 @timmer def home(name): '''這是home功能'''
    print('歡迎 %s 來到home頁面' %name) time.sleep(1) # home('qiuxi') # index()

print(index.__doc__) print(index.__name__)
改進五

但一個被裝飾的函數顯然不只僅只有這兩個功能,那每次都要往 wrapper 添加功能會很麻煩,因此Python提供了一個功能

from functools import wraps import time def timmer(func): @wraps(func) def wrapper(*args, **kwargs): start = time.time() res = func(*args, **kwargs) stop = time.time() print("執行時間是: %s" %(stop - start)) return res # 如今想把func下的屬性賦值給wrapper
    # Python提供了從functools中導入wraps
    # 在wrapper的上方加一個裝飾器wraps, func傳入wraps
    # 作的就是把func的一堆屬性賦值給wrapper
    return wrapper @timmer def index(): '''這是index功能'''
    print('歡迎來到index頁面') time.sleep(3) return 123 @timmer def home(name): '''這是home功能'''
    print('歡迎 %s 來到home頁面' %name) time.sleep(1) # 加了wraps後, 功能都是傳入的函數的了
print(help(index)) print(index.__name__)
改進六

至此,無參裝飾器即是如此

# 無參裝飾器的模版

# 這個func很是固定, 就是用來接收被裝飾的函數
def outter(func): # *和**就是用來接收被裝飾函數的參數, 原封不動的轉接給最原始的函數
    def wrapper(*args, **kwargs): res = func(*args, **kwargs) return res return wrapper

 

 應用:用裝飾器實現一個登陸認證功能

import time def auth(func): def wrapper(*args, **kwargs): username = input("請輸入用戶名: ").strip() password = input("請輸入密碼: ").strip() if username == 'egon' and password == '123': print("登陸成功") res = func(*args, **kwargs) return res else: print("用戶名或密碼錯誤") return wrapper @auth def index(): '''這是index功能'''
    print("歡迎來到index頁面") time.sleep(2) return 123 @auth def home(name): '''這是home功能'''
    print("歡迎 %s 來到home頁面" %name) time.sleep(1) index() home('qiuxi')
登陸功能

這個程序用來訪問每一個功能都須要登陸一次,與現實登陸狀況不符,能夠加一個可變類型的全局變量,用來記錄登陸的狀態,若是登陸了,就修改它的值,下一次實現不一樣的功能就進行判斷

# 對於不可變類型,能夠加一個global,將局部變量變爲全局變量 # 但最好不要用gloabl,這對於程序的嚴謹性並不友好

import time user_info = {'current_user': None} def auth(func): def wrapper(*args, **kwargs): if user_info['current_user'] is not None: res = func(*args, **kwargs) return res username = input("請輸入用戶名: ").strip() password = input("請輸入密碼: ").strip() if username == 'egon' and password == '123': # 記錄登陸狀態
            user_info['current_user'] = username print("登陸成功") res = func(*args, **kwargs) return res else: print("用戶名或密碼錯誤") return wrapper @auth def index(): '''這是index功能'''
    print("歡迎來到index頁面") time.sleep(2) return 123 @auth def home(name): '''這是home功能'''
    print("歡迎 %s 來到home頁面" %name) time.sleep(1) index() home('qiuxi')
改進一

如今想從不一樣的地方取出用戶名和密碼, 例如文件、數據庫、ldap, 只須要加上判斷

import time user_info = {'current_user': None} def auth(func): def wrapper(*args, **kwargs): if user_info['current_user'] is not None: res = func(*args, **kwargs) return res username = input("請輸入用戶名: ").strip() password = input("請輸入密碼: ").strip() # 加上判斷, 只須要控制engine就可讓認證功能執行不一樣的邏輯
        # engine是一個變量, 須要傳進來, 函數體須要外部傳進來一個值, 有兩種方案
        # 一個是參數形式, 這裏的engine是wrapper函數內的, 可是engine沒法經過wrapper傳進來,
        # 由於wrapper接收的參數是原封不動的給func使用, 而func是被裝飾的對象, 若是在這裏加了engine參數
        # 就是強制讓全部被裝飾的函數新增engine一個參數, 這顯然是不合理的
        # wrapper函數的外部函數還有一個參數func, 但engine經過這個外部函數的參數傳進來也是不行的
        # 由於func的功能也很單一, 就是用來接收被裝飾對象的, 因此也不能加其餘參數

        # 還有一種就是閉包函數形式
        if engine == 'file': if username == 'egon' and password == '123': # 記錄登陸狀態
                user_info['current_user'] = username print("登陸成功") res = func(*args, **kwargs) return res else: print("用戶名或密碼錯誤") elif engine == 'mysql': print("基於mysql數據庫的認證") elif engine == 'ldap': print("基於ldap的認證") else: print("沒法識別的認證") return wrapper @auth def index(): '''這是index功能'''
    print("歡迎來到index頁面") time.sleep(2) return 123 @auth def home(name): '''這是home功能'''
    print("歡迎 %s 來到home頁面" % name) time.sleep(1) index() home('qiuxi')
改進二

可見上面經過直接傳參的方式並不能實現功能,因而只能經過閉包函數形式傳入 engine

import time user_info = {'current_user': None} def auth2(engine = 'file'): def auth(func): def wrapper(*args, **kwargs): if user_info['current_user'] is not None: res = func(*args, **kwargs) return res username = input("請輸入用戶名: ").strip() password = input("請輸入密碼: ").strip() if engine == 'file': if username == 'egon' and password == '123': # 記錄登陸狀態
                    user_info['current_user'] = username print("登陸成功") res = func(*args, **kwargs) return res else: print("用戶名或密碼錯誤") elif engine == 'mysql': print("基於mysql數據庫的認證") elif engine == 'ldap': print("基於ldap的認證") else: print("沒法識別的認證") return wrapper return auth # 由於裝飾器仍是要用auth, 因此執行auth2, 傳入engine, # 拿到原來的auth的地址, 賦給新的auth
auth = auth2(engine = 'file') @auth def index(): '''這是index功能'''
    print("歡迎來到index頁面") time.sleep(2) return 123 @auth def home(name): '''這是home功能'''
    print("歡迎 %s 來到home頁面" % name) time.sleep(1) index() home('qiuxi')
改進四

上面代碼簡化

import time user_info = {'current_user': None} def auth2(engine = 'file'): def auth(func): def wrapper(*args, **kwargs): if user_info['current_user'] is not None: res = func(*args, **kwargs) return res username = input("請輸入用戶名: ").strip() password = input("請輸入密碼: ").strip() if engine == 'file': if username == 'egon' and password == '123': # 記錄登陸狀態
                    user_info['current_user'] = username print("登陸成功") res = func(*args, **kwargs) return res else: print("用戶名或密碼錯誤") elif engine == 'mysql': print("基於mysql數據庫的認證") elif engine == 'ldap': print("基於ldap的認證") else: print("沒法識別的認證") return wrapper return auth # auth = auth2(engine = 'file') # 因此 @auth能夠替換成 @auth2(engine = 'file') # 而後上面的一行 auth = auth2(engine = 'file') 就能夠刪除了
@auth2(engine = 'file') def index(): '''這是index功能'''
    print("歡迎來到index頁面") time.sleep(2) return 123 @auth2(engine = 'file') def home(name): '''這是home功能'''
    print("歡迎 %s 來到home頁面" % name) time.sleep(1) index() home('qiuxi')
改進六

因此最終實現的程序結果是

import time user_info = {'current_user': None} def auth2(engine = 'file'): def auth(func): def wrapper(*args, **kwargs): if user_info['current_user'] is not None: res = func(*args, **kwargs) return res username = input("請輸入用戶名: ").strip() password = input("請輸入密碼: ").strip() if engine == 'file': print("基於文件的認證") if username == 'egon' and password == '123': # 記錄登陸狀態
                    user_info['current_user'] = username print("登陸成功") res = func(*args, **kwargs) return res else: print("用戶名或密碼錯誤") elif engine == 'mysql': print("基於mysql數據庫的認證") elif engine == 'ldap': print("基於ldap的認證") else: print("沒法識別的認證") return wrapper return auth # 程序走到這行, 先無論@符號, 必定是去執行auth2 # 獲得一個auth內存地址, 而後再@auth # 而 @auth就是 auth(index), 即執行auth函數, 這裏的index是最原始的index的內存地址 # 獲得wrapper的內存地址做爲返回值, 而後把返回值賦給新的index
@auth2(engine = 'mysql') def index(): '''這是index功能'''
    print("歡迎來到index頁面") time.sleep(2) return 123 @auth2(engine = 'file') def home(name): '''這是home功能'''
    print("歡迎 %s 來到home頁面" % name) time.sleep(1) index() home('qiuxi')
最終版

 

補充:global 與 nonlocal

global是將局部變量添加global關鍵字變爲全局變量

x = 0 def f1(): x =1
    def f2(): x = 2
        def f3(): global x x = 3
            # print(x)
 f3() f2() f1() print(x) # 最終輸出結果爲3
global

nonlocal只能用於局部變量,找上層中離當前函數最近一層的局部變量

聲明瞭nonlocal的內部函數的變量修改會影響到離當前函數最近一層的局部變量

x = 0 def f1(): x = 1
    def f2(): # x = 2
        def f3(): nonlocal x print(x) f3() f2() f1() # 程序的執行結果爲1
nonlocal

 4、疊加多個裝飾器

加載裝飾器就是將原函數名換成裝飾器最內層的那個函數,在加載完畢後,調用原函數其實就是在調用最內層的函數。

import time def timmer(func): def wrapper1(*args, **kwargs): start = time.time() res = func(*args, **kwargs) stop = time.time() print("執行時間是: %s" % (stop - start)) return res return wrapper1 def auth(engine='file'): def xxx(func): def wrapper2(*args, **kwargs): name = input("用戶名: ").strip() pwd = input("密碼: ").strip() if engine == 'file': print("基於文件的認證") if name == "qiuxi" and pwd == "123": print("登陸成功") res = func(*args, **kwargs) return res elif engine == 'mysql': print("基於mysql認證") elif engine == 'ldap': print("基於ldap的認證") else: print("用戶識別錯誤") return wrapper2 return xxx # @auth(engine='file')
@timmer def index(): print("歡迎來到index頁面") time.sleep(2) index()
View Code

加載順序就是獲得wrapper的過程,執行順序就是執行wrapper函數的過程

假設只有一個timmer,加載就是@timmer到wrapper1的過程,調用原函數index就是在執行wrapper1函數

當一個被裝飾的對象同時疊加多個裝飾器的時候,裝飾器的加載順序是自下而上,裝飾器最內層的那個函數的執行順序是自上而下

 加載順序:

執行順序即程序的執行順序,自上而下,逐級細化

 裝飾器的前後順序不一樣,執行程序的時間也不一樣

相關文章
相關標籤/搜索