08裝飾器

  之前你有沒有這樣一段經歷:好久以前你寫過一個函數,如今你忽然有了個想法就是你想看看,之前那個函數在你數據集上的運行時間是多少,這時候你能夠修改以前代碼爲它加上計時的功能,可是這樣的話是否是還要大致讀讀你以前的這個的代碼,稍微搞清楚一點它的邏輯,纔敢給它添加新的東西。這樣是否是很繁瑣,要是你以前寫的代碼足夠亂足夠長,再去讀它是否是很抓狂...。實際工做中,咱們經常會遇到這樣的場景,可能你的需求還不僅是這麼簡單。那麼有沒有一種能夠不對源碼作任何修改,而且能夠很好的實現你全部需求的手段呢?答案固然是有,這就是今天咱們要介紹的python裝飾器。有了裝飾器,你除了不用擔憂前面提到的問題,而且還能夠很好的處理接下來要作的事:那就是如今你又有了一個新的需求,好比爲另外一個函數添加計時功能,這時就很是簡單了,把要裝飾的函數丟給裝飾器就行了,它會自動給你添加完功能並返回給你。是否是很神奇?下面咱們將一層層剝開它的神祕面紗。html

1. 閉包函數

在看裝飾器以前,咱們先來搞清楚什麼是閉包函數。python是一種面向對象的編程語言,在python中一切皆對象,這樣就使得變量所擁有的屬性,函數也一樣擁有。這樣咱們就能夠理解在函數內建立一個函數的行爲是徹底合法的。這種函數被叫作內嵌函數,這種函數只能夠在外部函數的做用域內被正常調用,在外部函數的做用域以外調用會報錯,例如:python

In [14]:
def outFunction():
    print('out side the function')
    def inFunction():
        print('inside the function')
    inFunction()
In [15]:
outFunction()
 
out side the function
inside the function
In [16]:
inFunction()
 
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-16-62c8de274b88> in <module>
----> 1inFunction()

NameError: name 'inFunction' is not defined
 

而若是內部函數裏引用了外部函數裏定義的對象(甚至是外層以外,但不是全局變量),那麼此時內部函數就被稱爲閉包函數。閉包函數所引用的外部定義的變量被叫作自由變量。閉包從語法上看很是簡單,可是卻有強大的做用。閉包能夠將其本身的代碼和做用域以及外部函數的做用結合在一塊兒。下面給出一個簡單的閉包的例子:編程

In [17]:
def count():
    a = 1
    b = 1
    def sum():
        c = 1
        return a + c  # a - 自由變量
    return sum  # 注意返回的是一個sum()函數對象
In [19]:
result = count()
result
Out[19]:
<function __main__.count.<locals>.sum()>
In [20]:
result()
Out[20]:
2
 

總結:什麼函數能夠被稱爲閉包函數呢?主要是知足兩點:函數內部定義的函數;引用了外部變量但非全局變量。閉包

 

2. python裝飾器

有了閉包函數的概念,咱們再去理解裝飾器會相對容易一些。python裝飾器本質上就是一個函數,它可讓其餘函數在不須要作任何代碼變更的前提下增長額外的功能,裝飾器的返回值也是一個函數對象(函數的指針)。裝飾器函數的外部函數傳入我要裝飾的函數名字,返回通過修飾後函數的名字;內層函數(閉包)負責修飾被修飾函數。從上面這段描述中咱們須要記住裝飾器的幾點屬性,以便後面能更好的理解:app

    實質: 是一個函數編程語言

    參數:是你要裝飾的函數名(並不是函數調用)ide

    返回:是裝飾完的函數名(也非函數調用)函數

    做用:爲已經存在的對象添加額外的功能性能

    特色:不須要對對象作任何的代碼上的變更測試

python裝飾器有不少經典的應用場景,好比:插入日誌、性能測試、事務處理、權限校驗等。裝飾器是解決這類問題的絕佳設計。而且從引入中的列子中咱們也能夠概括出:裝飾器最大的做用就是對於咱們已經寫好的程序,咱們能夠抽離出一些雷同的代碼組建多個特定功能的裝飾器,這樣咱們就能夠針對不一樣的需求去使用特定的裝飾器,這時由於源碼去除了大量泛化的內容而使得源碼具備更加清晰的邏輯。

2.1 函數裝飾器

函數的函數裝飾器 咱們仍是覺得函數添加計時功能爲例,講述函數裝飾器。

In [22]:
import time

def decorator(func):
    def wrapper(*args, **kwargs):  # 這個wrapper函數要替換掉原來的func函數,func函數可能有形參,也可能沒有形參,也可能有多個形參
        start_time = time.time()
        func()
        end_time = time.time()
        print(end_time - start_time)

    return wrapper


@decorator
def func():
    time.sleep(0.8)


func()  # 函數調用# 輸出:0.800644397735595
 
0.8000457286834717
 

在上面代碼中 func是我要裝飾器的函數,我想用裝飾器顯示func函數運行的時間。@decorator這個語法至關於 執行 func = decorator(func),爲func函數裝飾並返回。在來看一下咱們的裝飾器函數 - decorator,該函數的傳入參數是func (被裝飾函數),返回參數是內層函數。這裏的內層函數-wrapper,其實就至關於閉包函數,它起到裝飾給定函數的做用,wrapper參數爲args, **kwargs。args表示的參數以列表的形式傳入;**kwargs表示的參數以字典的形式傳入:

In [23]:
def test(*args, **kwargs):
    print('args:', end=' ')
    print(args)
    print('kwargs', end=' ')
    print(kwargs)
test(["world"], 'world', 1, a=1, b=2)
 
args: (['world'], 'world', 1)
kwargs {'a': 1, 'b': 2}
 

從圖中咱們能夠看到:凡是以key=value形式的參數均存在kwargs中,剩下的全部參數都以列表的形式存於args中。這裏要注意的是:爲了避免破壞原函數的邏輯,咱們要保證內層函數wrapper和被裝飾函數func的傳入參數和返回值類型必須保持一致。

 

2.2 類方法的函數裝飾器

類方法的函數裝飾器和函數的函數裝飾器相似。

In [24]:
import time

def decorator(func):
    def wrapper(me_instance):
        start_time = time.time()
        func(me_instance)
        end_time = time.time()
        print(end_time - start_time)
    return wrapper

class Method(object):
    @decorator
    def func(self):
        time.sleep(0.8)


p1 = Method()
p1.func()  # 函數調用
 
0.8000459671020508
 

對於類方法來講,都會有一個默認的參數self,它實際表示的是類的一個實例,因此在裝飾器的內部函數wrapper也要傳入一個參數 - me_instance就表示將類的實例p1傳給wrapper,其餘的用法都和函數裝飾器相同。

 

2.3 類裝飾器

前面咱們提到的都是讓 函數做爲裝飾器去裝飾其餘的函數或者方法,那麼可不可讓 一個類發揮裝飾器的做用呢?答案確定是能夠的,一切皆對象嚒,函數和類本質沒有什麼不同。類的裝飾器是什麼樣子的呢?

In [26]:
class Decorator(object):
    def __init__(self, f):
        self.f = f
        
    def __call__(self): # 這裏有注意的是:__call__()是一個特殊方法,它可將一個類實例變成一個可調用對象:
        print("decorator start")
        self.f()
        print("decorator end")

@Decorator
def func():
    print("func")

func()
 
decorator start
func
decorator end
In [27]:
p = Decorator(func)  # p是類Decorator的一個實例
p()  # 實現了__call__()方法後,p能夠被調用
 
decorator start
decorator start
func
decorator end
decorator end
 

2.4 裝飾器鏈

一個python函數也能夠被多個裝飾器修飾,要是有多個裝飾器時,這些裝飾器的執行順序是怎麼樣的呢?

In [57]:
def makebold(f):
    return lambda: '<b>' +f() + '</b>'
def makeitalic(f):
    return lambda: '<i>' +f() + '</i>'
In [58]:
@makebold
@makeitalic
def say():
    return "hello"
In [59]:
say()
Out[59]:
'<b><i>hello</i></b>'
 

可見,多個裝飾器的執行順序:是從近到遠依次執行。

 

2.5 python裝飾器庫 - functools

In [62]:
def decorator(func):
    def inner_function():
        pass
    return inner_function

@decorator
def func():
    pass

func.__name__
Out[62]:
'inner_function'
 

述代碼最後執行的結果不是 func,而是 inner_function!這表示被裝飾函數自身的信息丟失了!怎麼才能避免這種問題的發生呢?

能夠藉助functools.wraps()函數:

In [63]:
from functools import wraps

def decorator(func):
    @wraps(func)
    def inner_function():
        pass
    return inner_function

@decorator
def func():
    pass

func.__name__
Out[63]:
'func'
相關文章
相關標籤/搜索