国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
一文看懂Python系列之裝飾器(decorator)(工作面試必讀)

Python的裝飾器(decorator)可以說是Python的一個(gè)神器,它可以在不改變一個(gè)函數(shù)代碼和調(diào)用方式的情況下給函數(shù)添加新的功能。Python的裝飾器同時(shí)也是Python學(xué)習(xí)從入門到精通過程中必需要熟練掌握的知識(shí)。小編我當(dāng)初學(xué)習(xí)Python時(shí)差點(diǎn)被裝飾器搞暈掉,今天嘗試用淺顯的語(yǔ)言解釋下Python裝飾器的工作原理及如何編寫自己的裝飾器吧。

Python裝飾器的本質(zhì)

Python的裝飾器本質(zhì)上是一個(gè)嵌套函數(shù),它接受被裝飾的函數(shù)(func)作為參數(shù),并返回一個(gè)包裝過的函數(shù)。這樣我們可以在不改變被裝飾函數(shù)的代碼的情況下給被裝飾函數(shù)或程序添加新的功能。Python的裝飾器廣泛應(yīng)用于緩存、權(quán)限校驗(yàn)(如django中的@login_required和@permission_required裝飾器)、性能測(cè)試(比如統(tǒng)計(jì)一段程序的運(yùn)行時(shí)間)和插入日志等應(yīng)用場(chǎng)景。有了裝飾器,我們就可以抽離出大量與函數(shù)功能本身無關(guān)的代碼,增加一個(gè)函數(shù)的重用性。

試想你寫了很多程序,一直運(yùn)行也沒啥問題。有一天老板突然讓你統(tǒng)計(jì)每個(gè)程序都運(yùn)行了多長(zhǎng)時(shí)間并比較下運(yùn)行效率。此時(shí)如果你去手動(dòng)修改每個(gè)程序的代碼一定會(huì)讓你抓狂,而且還破壞了那些程序的重用性。聰明的程序員是絕不能干這種蠢事的。此時(shí)你可以編寫一個(gè)@time_it的裝飾器(代碼如下所示)。如果你想打印出某個(gè)函數(shù)或程序運(yùn)行時(shí)間,只需在函數(shù)前面@一下,是不是很帥?

import time

def time_it(func):
   def inner():
       start = time.time()
       func()
       end = time.time()
       print('用時(shí):{}'.format(end-start))
   return inner

@time_it
def func1():
   time.sleep(2)
   print('Func1 is running.')

if __name__ == '__main__':
   func1()

運(yùn)行結(jié)果如下:

Func1 is running.

用時(shí):2.0056326389312744

由于Python裝飾器的工作原理主要依賴于嵌套函數(shù)和閉包,所以我們必須先對(duì)嵌套函數(shù)和閉包有深入的了解。嵌套函數(shù)和閉包幾乎是Python工作面試必考題哦。

嵌套函數(shù)

如果在一個(gè)函數(shù)的內(nèi)部還定義了另一個(gè)函數(shù)(注意: 是定義,不是引用!),這個(gè)函數(shù)就叫嵌套函數(shù)。外部的我們叫它外函數(shù),內(nèi)部的我們叫他內(nèi)函數(shù)。

我們先來看一個(gè)最簡(jiǎn)單的嵌套函數(shù)的例子。我們?cè)趏uter函數(shù)里又定義了一個(gè)inner函數(shù),并調(diào)用了它。你注意到了嗎? 內(nèi)函數(shù)在自己作用域內(nèi)查找局部變量失敗后,會(huì)進(jìn)一步向上一層作用域里查找。

def outer():
   x = 1
   
def inner():
       y = x + 1
       
print(y)
   inner()

outer() #輸出結(jié)果 2

如果我們?cè)谕夂瘮?shù)里不直接調(diào)用內(nèi)函數(shù),而是通過return inner返回一個(gè)內(nèi)函數(shù)的引用 這時(shí)會(huì)發(fā)生什么呢? 你將會(huì)得到一個(gè)內(nèi)函數(shù)對(duì)象,而不是運(yùn)行結(jié)果。

def outer():
   x = 1
   
def inner():
       y = x + 1
       
print(y)
   return inner

outer() # 輸出<function outer.<locals>.inner at 0x039248E8>
f1 = outer()
f1() # 輸出2

上述這個(gè)案例比較簡(jiǎn)單,因?yàn)閛uter和inner函數(shù)都是沒有參數(shù)的。我們現(xiàn)在對(duì)上述代碼做點(diǎn)改動(dòng),加入?yún)?shù)。你可以看到外函數(shù)的參數(shù)或變量可以很容易傳遞到內(nèi)函數(shù)。


def outer(x):
   a = x

   def inner(y):
       b = y
       print(a+b)

   return inner

f1 = outer(1) # 返回inner函數(shù)對(duì)象
f1(10) # 相當(dāng)于inner(10)。輸出11

如果上例中外函數(shù)的變量x換成被裝飾函數(shù)對(duì)象(func),內(nèi)函數(shù)的變量y換成被裝飾函數(shù)的參數(shù),我們就可以得到一個(gè)通用的裝飾器啦(如下所示)。你注意到了嗎? 我們?cè)跊]對(duì)func本身做任何修改的情況下,添加了其它功能, 從而實(shí)現(xiàn)了對(duì)函數(shù)的裝飾。


def decorator(func):
   def inner(*args, **kwargs):
       add_other_actions()
       return func(*args, **kwargs)
   return inner

請(qǐng)你仔細(xì)再讀讀上面這段代碼,我們的decorator返回的僅僅是inner函數(shù)嗎? 答案是不。它返回的其實(shí)是個(gè)閉包(Closure)。整個(gè)裝飾器的工作都依賴于Python的閉包原理。

閉包(Closure)

閉包是Python編程一個(gè)非常重要的概念。如果一個(gè)外函數(shù)中定義了一個(gè)內(nèi)函數(shù),且內(nèi)函數(shù)體內(nèi)引用到了體外的變量,這時(shí)外函數(shù)通過return返回內(nèi)函數(shù)的引用時(shí),會(huì)把定義時(shí)涉及到的外部引用變量和內(nèi)函數(shù)打包成一個(gè)整體(閉包)返回。我們?cè)诳聪轮g案例。我們的outer方法返回的只是內(nèi)函數(shù)對(duì)象嗎? 錯(cuò)。我們的outer函數(shù)返回的實(shí)際上是一個(gè)由inner函數(shù)和外部引用變量(a)組成的閉包!


def outer(x):
   a = x

   def inner(y):
       b = y
       print(a+b)

   return inner


f1 = outer(1) # 返回inner函數(shù)對(duì)象+局部變量1(閉包)
f1(10) # 相當(dāng)于inner(10)。輸出11

一般一個(gè)函數(shù)運(yùn)行結(jié)束的時(shí)候,臨時(shí)變量會(huì)被銷毀。但是閉包是一個(gè)特別的情況。當(dāng)外函數(shù)發(fā)現(xiàn),自己的臨時(shí)變量會(huì)在將來的內(nèi)函數(shù)中用到,自己在結(jié)束的時(shí)候,返回內(nèi)函數(shù)的同時(shí),會(huì)把外函數(shù)的臨時(shí)變量同內(nèi)函數(shù)綁定在一起。這樣即使外函數(shù)已經(jīng)結(jié)束了,內(nèi)函數(shù)仍然能夠使用外函數(shù)的臨時(shí)變量。這就是閉包的強(qiáng)大之處。

如何編寫一個(gè)通用的裝飾器

我們現(xiàn)在可以開始動(dòng)手寫個(gè)名為hint的裝飾器了,其作用是在某個(gè)函數(shù)運(yùn)行前給我們提示。這里外函數(shù)以hint命名,內(nèi)函數(shù)以常用的wrapper(包裹函數(shù))命名。

def hint(func):
   def wrapper(*args, **kwargs):
       print('{} is running'.format(func.__name__))
       return func(*args, **kwargs)
   return wrapper

@hint
def hello():
   print('Hello!')

我們現(xiàn)在對(duì)hello已經(jīng)進(jìn)行了裝飾,當(dāng)我們調(diào)用hello()時(shí),我們可以看到如下結(jié)果。

>>> hello()
hello is running.
Hello!

值得一提的是被裝飾器裝飾過的函數(shù)看上去名字沒變,其實(shí)已經(jīng)變了。當(dāng)你運(yùn)行hello()后,你會(huì)發(fā)現(xiàn)它的名字已經(jīng)悄悄變成了wrapper,這顯然不是我們想要的(如下圖所示)。這一點(diǎn)也不奇怪,因?yàn)橥夂瘮?shù)返回的是由wrapper函數(shù)和其外部引用變量組成的閉包。

>>> hello.__name__
'wrapper'

為了解決這個(gè)問題保證裝飾過的函數(shù)__name__屬性不變,我們可以使用functools模塊里的wraps方法,先對(duì)func變量進(jìn)行wraps。下面這段代碼可以作為編寫一個(gè)通用裝飾器的示范代碼,注意收藏哦。


from functools import wraps

def hint(func):
   @wraps(func)
   def wrapper(*args, **kwargs):
       print('{} is running'.format(func.__name__))
       return func(*args, **kwargs)
   return wrapper


@hint
def hello():
   print('Hello!')

恭喜你,你已經(jīng)學(xué)會(huì)寫一個(gè)比較通用的裝飾器啦,并保證裝飾過的函數(shù)__name__屬性不變啦。當(dāng)然使用嵌套函數(shù)也有缺點(diǎn),比如不直觀。這時(shí)你可以借助Python的decorator模塊(需事先安裝)可以簡(jiǎn)化裝飾器的編寫和使用。如下所示。

from decorator import decorator

@decorator
def hint(func, *args, **kwargs):
   print('{} is running'.format(func.__name__))
   return func(*args, **kwargs)

編寫帶參數(shù)的高級(jí)裝飾器

前面幾個(gè)裝飾器一般是內(nèi)外兩層嵌套函數(shù)。如果我們需要編寫的裝飾器本身是帶參數(shù)的,我們需要編寫三層的嵌套函數(shù),其中最外一層用來傳遞裝飾器的參數(shù)。現(xiàn)在我們要對(duì)@hint裝飾器做點(diǎn)改進(jìn),使其能通過@hint(coder='John')傳遞參數(shù)。該裝飾器在函數(shù)運(yùn)行前給出提示的時(shí)候還顯示函數(shù)編寫人員的名字。完整代碼如下所示:

from functools import wraps


def hint(coder):
   def wrapper(func):
       @wraps(func)
       def inner_wrapper(*args, **kwargs):
           print('{} is running'.format(func.__name__))
           print('Coder: {}'.format(coder))
           return func(*args, **kwargs)
       return inner_wrapper
   return wrapper


@hint(coder='John')
def hello():
   print('Hello!')

下面這段代碼是一段經(jīng)典的Python裝飾器代碼,顯示了@cache這個(gè)裝飾器怎么編寫和工作的。它需要使用緩存實(shí)例做為一個(gè)參數(shù),所以也是三層嵌套函數(shù)。

import time
from functools import wraps


# 裝飾器增加緩存功能
def cache(instance):
   def wrapper(func):
       @wraps(func)
       def inner_wrapper(*args, **kwargs):
           # 構(gòu)建key: key => func_name::args::kwargs
           
joint_args = ','.join((str(x) for x in args))
           joint_kwargs = ','.join('{}={}'.format(k, v) for k, v in sorted(kwargs.items()))
           key = '{}::{}::{}'.format(func.__name__,joint_args, joint_kwargs)
           # 根據(jù)key獲取結(jié)果。如果key已存在直接返回結(jié)果,不用重復(fù)計(jì)算。
       
result = instance.get(key)
           if result is not None:
               return result
           # 如果結(jié)果不存在,重新計(jì)算,緩存。
       
result = func(*args, **kwargs)
           instance.set(key, result)
           return result
       return inner_wrapper
   return wrapper


# 創(chuàng)建字典構(gòu)造函數(shù),用戶緩存K/V鍵值對(duì)
class DictCache:
   def __init__(self):
       self.cache = dict()

   def get(self, key):
       return self.cache.get(key)

   def set(self, key, value):
       self.cache[key] = value

   def __str__(self):
       return str(self.cache)

   def __repr__(self):
       return repr(self.cache)


# 創(chuàng)建緩存對(duì)象
cache_instance = DictCache()


# Python語(yǔ)法糖調(diào)用裝飾器
@cache(cache_instance)
def long_time_func(x):
   time.sleep(x)
   return x

# 調(diào)用裝飾過函數(shù)
long_time_func(3)

基于類實(shí)現(xiàn)的裝飾器

Python的裝飾器不僅可以用嵌套函數(shù)來編寫,還可以使用類來編寫。其調(diào)用__init__方法創(chuàng)建實(shí)例,傳遞參數(shù),并調(diào)用__call__方法實(shí)現(xiàn)對(duì)被裝飾函數(shù)功能的添加。

from functools import wraps


#類的裝飾器寫法, 不帶參數(shù)
class Hint(object):
   def __init__(self, func):
       self.func = func

   def __call__(self, *args, **kwargs):
       print('{} is running'.format(self.func.__name__))
       return self.func(*args, **kwargs)


#類的裝飾器寫法, 帶參數(shù)
class Hint(object):
   def __init__(self, coder=None):
       self.coder = coder

   def __call__(self, func):
       @wraps(func)
       def wrapper(*args, **kwargs):
           print('{} is running'.format(func.__name__))
           print('Coder: {}'.format(self.coder))
           return func(*args, **kwargs)     # 正式調(diào)用主要處理函數(shù)
       
return wrapper

小結(jié)

本文總結(jié)了什么是Python的裝飾器及其工作原理,并重點(diǎn)介紹了嵌套函數(shù)和閉包原理。最后詳細(xì)展示了如何編寫一個(gè)通用裝飾器及帶參數(shù)的高級(jí)裝飾器, 包括使用類來編寫裝飾器。大家要熟練掌握哦??床欢目梢韵燃尤胛⑿攀詹匾院笤俜磸?fù)閱讀。

大江狗

2018.11.29

參考資料

  • https://www.cnblogs.com/Lin-Yi/p/7305364.html

  • http://python.jobbole.com/81683/

  • http://lib.csdn.net/article/python/62942

  • http://lib.csdn.net/article/python/64769

  • http://www.cnblogs.com/cicaday/p/python-decorator.html

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
裝飾器_裝飾器flage = true
【連載電子書五】Python函數(shù)編程(下)
「Python小腳本」基于裝飾器的函數(shù)日志腳本
沒看完這11 條,別說你精通 Python 裝飾器
Python學(xué)習(xí)—裝飾器
閉包函數(shù)與裝飾器
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服