真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

Python裝飾器

一、裝飾器介紹

1.1 為何要用裝飾器

軟件的設(shè)計(jì)應(yīng)該遵循開放封閉原則,即對擴(kuò)展是開放的,而對修改是封閉的:

為企業(yè)提供成都網(wǎng)站建設(shè)、成都網(wǎng)站制作、網(wǎng)站優(yōu)化、網(wǎng)絡(luò)營銷推廣、競價(jià)托管、品牌運(yùn)營等營銷獲客服務(wù)。成都創(chuàng)新互聯(lián)公司擁有網(wǎng)絡(luò)營銷運(yùn)營團(tuán)隊(duì),以豐富的互聯(lián)網(wǎng)營銷經(jīng)驗(yàn)助力企業(yè)精準(zhǔn)獲客,真正落地解決中小企業(yè)營銷獲客難題,做到“讓獲客更簡單”。自創(chuàng)立至今,成功用技術(shù)實(shí)力解決了企業(yè)“網(wǎng)站建設(shè)、網(wǎng)絡(luò)品牌塑造、網(wǎng)絡(luò)營銷”三大難題,同時(shí)降低了營銷成本,提高了有效客戶轉(zhuǎn)化率,獲得了眾多企業(yè)客戶的高度認(rèn)可!

  • 對擴(kuò)展開放,意味著有新的需求或變化時(shí),可以對現(xiàn)有代碼進(jìn)行擴(kuò)展,以適應(yīng)新的情況。

  • 對修改封閉,意味著對象一旦設(shè)計(jì)完成,就可以獨(dú)立完成其工作,而不要對其進(jìn)行修改。

軟件包含的所有功能的源代碼以及調(diào)用方式,都應(yīng)該避免修改,否則一旦改錯(cuò),則極有可能產(chǎn)生連鎖反應(yīng),最終導(dǎo)致程序崩潰。而對于上線后的軟件,新需求或者變化又層出不窮,我們必須為程序提供擴(kuò)展的可能性,這就用到了裝飾器。

1.2 什么是裝飾器

‘裝飾’代指為被裝飾對象添加新的功能,‘器’代指器具/工具,裝飾器與被裝飾的對象均可以是任意可調(diào)用對象。

概括的講,裝飾器的作用就是在不修改被裝飾對象源代碼和調(diào)用方式的前提下為被裝飾對象添加額外的功能。

裝飾器經(jīng)常用于有切面需求的場景,比如:插入日志、性能測試、事務(wù)處理、緩存、權(quán)限校驗(yàn)等應(yīng)用場景,裝飾器是解決這類問題的絕佳設(shè)計(jì)。有了裝飾器,就可以抽離出大量與函數(shù)功能本身無關(guān)的雷同代碼并繼續(xù)重用。

提示:可調(diào)用對象有函數(shù),方法或者類,此處我們單以本篇主題:函數(shù)為例,來介紹函數(shù)裝飾器,并且被裝飾的對象也是函數(shù)。

二、裝飾器的實(shí)現(xiàn)

函數(shù)裝飾器分為:無參裝飾器和有參裝飾器兩種,二者的實(shí)現(xiàn)原理一樣,都是‘函數(shù)嵌套+閉包+函數(shù)對象’的組合使用的產(chǎn)物。

2.1 無參裝飾器的實(shí)現(xiàn)

如果想為下述函數(shù)添加統(tǒng)計(jì)其執(zhí)行時(shí)間的功能:

import time
 
def index():
    time.sleep(3)
    print('Welcome to the index page')
    return 200
 
index()  # 函數(shù)執(zhí)行

遵循不修改被裝飾對象源代碼的原則,我們想到的解決方法可能是這樣:

start_time = time.time()
index()  # 函數(shù)執(zhí)行
stop_time = time.time()
print('run time is %s' % (stop_time - start_time))

考慮到還有可能要統(tǒng)計(jì)其他函數(shù)的執(zhí)行時(shí)間,于是我們將其做成一個(gè)單獨(dú)的工具,函數(shù)體需要外部傳入被裝飾的函數(shù)從而進(jìn)行調(diào)用,我們可以使用參數(shù)的形式傳入:

def wrapper(func):  # 通過參數(shù)接收外部的值
    start_time = time.time()
    res = func()
    stop_time = time.time()
    print('run time is %s' % (stop_time - start_time))
    return res

但之后函數(shù)的調(diào)用方式都需要統(tǒng)一改成:

wrapper(index)
wrapper(其他函數(shù))

這便違反了不能修改被裝飾對象調(diào)用方式的原則,于是我們需要換一種為函數(shù)體傳值的方式,那之前所學(xué)的閉包函數(shù)在這里就能派上用場了,修改如下:

def timer(func):
    def wrapper():  # 引用外部作用域的變量func
        start_time = time.time()
        res = func()
        stop_time = time.time()
        print('run time is %s' % (stop_time - start_time))
        return res
    return wrapper

這樣我們便可以在不修改被裝飾函數(shù)源代碼和調(diào)用方式的前提下為其加上統(tǒng)計(jì)時(shí)間的功能,只不過需要事先執(zhí)行一次timer將被裝飾的函數(shù)傳入,返回一個(gè)閉包函數(shù)wrapper重新賦值給變量名/函數(shù)名index,如下:

index = timer(index) 
index() 
"""
第1行得到index = wrapper,wrapper攜帶對外作用域的引用:func = 原始的index
第2行執(zhí)行的是wrapper(),在wrapper的函數(shù)體內(nèi)再執(zhí)行最原始的index
"""

至此我們便實(shí)現(xiàn)了一個(gè)無參裝飾器timer,可以在不修改被裝飾對象index源代碼和調(diào)用方式的前提下為其加上新功能。但我們忽略了若被裝飾的函數(shù)是一個(gè)有參函數(shù),便會(huì)拋出異常:

def home(name):
    time.sleep(5)
    print('Welcome to the home page', name)
 
home = timer(home)
home('jason')
# 拋出異常
TypeError: wrapper() takes 0 positional arguments but 1 was given 

之所以會(huì)拋出異常,是因?yàn)閔ome(‘jason’)調(diào)用的其實(shí)是wrapper(‘jason’),而函數(shù)wrapper沒有參數(shù)。wrapper函數(shù)接收的參數(shù)其實(shí)是給最原始的func用的,為了能滿足被裝飾函數(shù)參數(shù)的所有情況,便用上*args和**kwargs組合 ,于是修正后的裝飾器timer如下:

def timer(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        res = func(*args, **kwargs)
        stop_time = time.time()
        print('run time is %s' % (stop_time - start_time))
        return res
    return wrapper

此時(shí)我們就可以用timer來裝飾帶參數(shù)或不帶參數(shù)的函數(shù)了,但是為了簡潔而優(yōu)雅地使用裝飾器,Python提供了專門的裝飾器語法糖來取代index = timer(index)的形式,需要在被裝飾對象的正上方單獨(dú)一行添加@timer(語法糖必須緊貼在被裝飾對象的上方);

當(dāng)解釋器解釋到@timer時(shí)就會(huì)調(diào)用timer函數(shù),且把它正下方的函數(shù)名當(dāng)做實(shí)參傳入,然后將返回的結(jié)果重新賦值給原函數(shù)名:

@timer    """index = timer(index)"""
def index():
    time.sleep(3)
    print('Welcome to the index page')
    return 200

@timer    """index = timer(home)"""
def home(name):
    time.sleep(5)
    print('Welcome to the home page', name)

裝飾器語法糖內(nèi)部原理:會(huì)自動(dòng)將下面緊貼著的被裝飾對象名字當(dāng)做參數(shù)傳給裝飾器調(diào)用。

如果我們有多個(gè)裝飾器,可以疊加多個(gè):

@deco3
@deco2
@deco1
def index():
    pass

疊加多個(gè)裝飾器也無特殊之處,上述代碼語義如下:

index = deco3(deco2(deco1(index)))

2.2 有參裝飾器的實(shí)現(xiàn)

了解無參裝飾器的實(shí)現(xiàn)原理后,我們可以再實(shí)現(xiàn)一個(gè)用來為被裝飾對象添加認(rèn)證功能的裝飾器,實(shí)現(xiàn)的基本形式如下:

def deco(func):
    def wrapper(*args, **kwargs):
        編寫基于文件的認(rèn)證,認(rèn)證通過則執(zhí)行res = func(*args,**kwargs),并返回res
    return wrapper

如果我們想提供多種不同的認(rèn)證方式以供選擇,單從wrapper函數(shù)的實(shí)現(xiàn)角度改寫如下:

def deco(func):
	def wrapper(*args, **kwargs):
        if driver == 'file':
            編寫基于文件的認(rèn)證,認(rèn)證通過則執(zhí)行res = func(*args, **kwargs),并返回res
        elif driver == 'mysql':
            編寫基于mysql認(rèn)證,認(rèn)證通過則執(zhí)行res = func(*args, **kwargs),并返回res
	return wrapper

函數(shù)wrapper需要一個(gè)driver參數(shù),而函數(shù)deco與wrapper的參數(shù)都有其特定的功能,不能用來接收其他類別的參數(shù),既然需要一個(gè)參數(shù),那我們可以再在deco的外部再包一層函數(shù)auth,用來專門接收額外的參數(shù),這樣便保證了在auth函數(shù)內(nèi)無論多少層都可以引用到:

def auth(driver):
    def deco(func):
        ……
    return deco

此時(shí)我們就實(shí)現(xiàn)了一個(gè)有參裝飾器,使用方式如下:

"""先調(diào)用auth(driver='file'),得到@deco,deco是一個(gè)閉包函數(shù),包含了對外部作用域名字driver的引用,@deco的語法意義與無參裝飾器一樣"""
@auth(driver='file')
def index():
    pass
 
"""再調(diào)用auth(driver='mysql'),后續(xù)同上"""    
@auth(driver='mysql')
def home():
    pass

可以使用help(函數(shù)名)來查看函數(shù)的文檔注釋,本質(zhì)就是查看函數(shù)的doc屬性,但對于被裝飾之后的函數(shù),查看文檔注釋:

@timer
def home(name):
    '''
    home page function
    :param name: str
    :return: None
    '''
    time.sleep(5)
    print('Welcome to the home page', name)
    
print(help(home))
"""
打印結(jié)果:
 
Help on function wrapper in module __main__:
 
wrapper(*args, **kwargs)
 
None
"""

在被裝飾之后home = wrapper,查看home.name也可以發(fā)現(xiàn)home的函數(shù)名其實(shí)是wrapper,如果想要保留原函數(shù)的文檔和函數(shù)名屬性,就需要修正裝飾器:

def timer(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        res = func(*args, **kwargs)
        stop_time = time.time()
        print('run time is %s' % (stop_time - start_time))
        return res
    wrapper.__doc__ = func.__doc__
    wrapper.__name__ = func.__name__
    return wrapper

可以看到,按照上述方式來實(shí)現(xiàn)保留原函數(shù)屬性過于麻煩,functools模塊下提供一個(gè)裝飾器wraps專門用來幫我們實(shí)現(xiàn)這件事,用法如下:

from functools import wraps
 
def timer(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        res = func(*args, **kwargs)
        stop_time = time.time()
        print('run time is %s' % (stop_time - start_time))
        return res
    return wrapper

網(wǎng)站欄目:Python裝飾器
轉(zhuǎn)載注明:http://weahome.cn/article/dsojsci.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部