首頁 >後端開發 >Python教學 >Python裝飾器詳解

Python裝飾器詳解

高洛峰
高洛峰原創
2016-12-01 15:55:172234瀏覽

 python中的裝飾器是一個用得非常多的東西,我們可以把一些特定的方法、通用的方法寫成一個個裝飾器,這就為調用這些方法提供一個非常大的便利,如此提高我們代碼的可讀性以及簡潔性,以及可擴展性。

在學習python裝飾器之前我們先看看這樣一個例子:

一、作用域

# coding:utf-8
 
msg = 'hello test1'
 
 
def add():
    msg = 'this is add'
    print msg     #当执行add()时将打印'this is add'
def add2():
    print msg     #当执行add2()时将打印'hello test1'

    上面的例子簡單地對python的作用域做了一個說明,例子中申明了全局變量msg,add函數中也申明了一個局部變量msg,當執行add()的"print msg" 時會先找add裡面是否有局部變量msg,如果沒找到就去上一級作用域找是否存在該變量,局部變量在函數運作時生成,當函數結束運行時局部變數也隨之銷毀,接下來我們對上面的例子加深:

二、閉包

# coding:utf-8
 
def add():
    msg = 'hello add'
    def inner():
        print msg               #运行add()这里会打印'hello add'
    return inner

    

>>> obj = add()
>>> obj            #可见obj是一个指向inner函数的对象
<function inner at 0x0000000002855438>
...
>>> obj()
hello add           #运行obj()打印msg

   

〜的例子有一點點疑惑,obj是指向inner函數的對象,當運行obj時就等同於運行inner,但是add函數並沒有運行,也就是說msg沒有被申明同時inner也沒有申明變量msg,它又是如何找到msg這個變量的值呢?

  這就是python裡的"閉包",Python支援一個叫做函數閉包的特性,用人話來講就是,嵌套定義在非全局作用域裡面的函數能夠記住它在被定義的時候它所處的封閉命名空間。這能夠透過查看函數的obj.func_closure屬性得出結論,這個屬性裡麵包含封閉作用域裡面的值(只會包含被捕捉到的值,如果在add裡面還定義了其他的值,封閉作用域裡面是不會的)閉包就是python裝飾器的核心原理,接下來我們寫一個簡單的裝飾器範例:

三、簡單的裝飾器

# coding:utf-8
 
def check_args_num(func):
    # 该装饰器用于检查传入的参数数量,检查是否是两个参数
    def inner(*args, **kwargs):
        args_list = list(args)
        if len(args_list) < 2:
 
            for i in range(2 - len(args)):
                # 如果参数数量小于2则用0填补
                args_list.append(0)
        if len(args_list) > 2:
            # 如果参数数量大于2则打印错误
            print &#39;The args number is too many!&#39;
        func(*args_list, **kwargs)
    return inner
 
 
@check_args_num
def add(x, y):
    return x + y

   

四、多個裝飾器

>>>print add(1,2)
3
...
>>>print add(100)
100
...
>>>print add(1,2,3)
Traceback (most recent call last):
  File "D:\PyCharm 5.0.4\helpers\pydev\pydevd_exec.py", line 3, in Exec
    exec exp in global_vars, local_vars
  File "<input>", line 1, in <module>
  File "E:/code/my_project/decorator/test1.py", line 14, in inner
    raise Exception(&#39;The args number is too many!&#39;)
Exception: The args number is too many!
...
>>>add
<function inner at 0x0000000002A6C3C8>
#可以看到add函数现在指向的是inner

   

這裡多加了一個參數內容檢查的裝飾器,當多個裝飾器時,會自上而下執行,先執行check_args_num再執行check_args_int,執行結果:

# coding:utf-8
 
def check_args_int(func):
    # 该装饰器用于检查传入的参数是否是int型
    def ensure_int(*args, **kwargs):
        from array import array
        try:
            array(&#39;H&#39;, args)
        except Exception, e:
            raise Exception(e)
        return func(*args, **kwargs)
 
    return ensure_int
 
 
def check_args_num(func):
    # 该装饰器用于检查传入的参数数量,检查是否是两个参数
    def inner(*args, **kwargs):
        args_list = list(args)
        if len(args_list) < 2:
 
            for i in range(2 - len(args)):
                # 如果参数数量小于2则用0填补
                args_list.append(0)
        if len(args_list) > 2:
            # 如果参数数量大于2则打印错误
            raise Exception(&#39;The args number is too many!&#39;)
        return func(*args_list, **kwargs)
 
    return inner
 
 
@check_args_num
@check_args_int
def add(x, y):
    return x + y

#這裡可以看到add還是指向inner,我們可以這樣理解當多個裝飾器存在時,當調用add時,其調用入口始終是第一個裝飾器,第一個裝飾器執行完,再執行下一個,同時也會將參數依序傳遞下去

   

五、帶參數的裝飾器

我們知道定義裝飾器的時候傳入裝飾器的第一個參數是被裝飾的函數(eg.例子中的add),有些時候我們也需要給裝飾器傳遞額外的參數,下面例子將給裝飾器傳遞額外參數,如下:

>>> print add(1,&#39;fsaf&#39;)
Traceback (most recent call last):
  File "D:\PyCharm 5.0.4\helpers\pydev\pydevd_exec.py", line 3, in Exec
    exec exp in global_vars, local_vars
  File "<input>", line 1, in <module>
  File "E:/code/my_project/decorator/test1.py", line 28, in inner
    return func(*args_list, **kwargs)
  File "E:/code/my_project/decorator/test1.py", line 10, in ensure_int
    raise Exception(e)
Exception: an integer is required
...
>>> add
<function inner at 0x0000000002B1C4A8>

   

這個例子只有check_args_num和之前的有所不同,在於不同在於裝飾器check_args_num多了一個參數flag,當flag=='false'時,跳過參數數量檢查,下面是輸出結果

# coding:utf-8
 
def check_args_int(func):
    # 该装饰器用于检查传入的参数是否是int型
    def ensure_int(*args, **kwargs):
        from array import array
        try:
            array(&#39;H&#39;, args)
        except Exception, e:
            raise Exception(e)
        return func(*args, **kwargs)
 
    return ensure_int
 
 
def check_args_num(flag):
    &#39;&#39;&#39;
    :param func: 被装饰函数
    :param flag: 决定是否检查参数数量
    &#39;&#39;&#39;
 
    # 该装饰器用于检查传入的参数数量,检查是否是两个参数
    def get_func(func):
 
        def inner(*args, **kwargs):
            if flag == &#39;false&#39;:
                print &#39;Skip check !&#39;
                return func(*args, **kwargs)
            args_list = list(args)
            if len(args_list) < 2:
 
                for i in range(2 - len(args)):
                    # 如果参数数量小于2则用0填补
                    args_list.append(0)
            if len(args_list) > 2:
                # 如果参数数量大于2则打印错误
                raise Exception(&#39;The args number is too many!&#39;)
            return func(*args_list, **kwargs)
 
        return inner
 
    return get_func
 
 
@check_args_num(&#39;false&#39;)
@check_args_int
def add(x, y):
    return x + y

   

六、decorator不帶參數的裝飾器

六、decorator不帶參數的裝飾器

六、decorator不帶參數的裝飾器

ator

用來專門封裝裝飾器的模組,使用decorator構造裝飾器更加簡便,同時被裝飾的函數簽名也保留不變

  之前的講了通過閉包實現python裝飾器構造,這裡利用decorator模組實現python裝飾器與其原理都是一樣的

>>>print add(1, 2)
Skip check !
3

    

from decorator import decorator
 
 
@decorator
def check_num(func, *args, **kwargs):
    if len(args) != 2:
        raise Exception(&#39;Your args number is not two&#39;)
    else:
        return func(*args, **kwargs)
 
 
@check_num
def add(x, y):
    return x + y

   

從上面的例子可以看出,透過閉包的方式構造裝飾器時,其執行入口是裝飾器中的嵌套函數,這樣就可能出現上面出現的問題,當add(1,2,3)執行時會先執行inner函數(如果inner裡面沒有參數校驗則這裡是不會拋出異常的,只有當執行到return func(*args,* *kwargs)時才真正呼叫add(x,y)函數,這時候才會拋出異常)這樣就會造成程式執行多餘的程式碼,浪費內存,cpu。

七、decorator帶參數的裝飾器

如果想讓裝飾器帶參數呢?

>>> add
<function add at 0x0000000002D43BA8>
>>> print add(1,2)
3
...
>>> add(1,2,3)
Traceback (most recent call last):
  File "D:\PyCharm 5.0.4\helpers\pydev\pydevd_exec.py", line 3, in Exec
    exec exp in global_vars, local_vars
  File "<input>", line 1, in <module>
TypeError: add() takes exactly 2 arguments (3 given)
 
#可以看到这里当我们传三个参数给add()函数时,他直接从add()函数抛出类型错误异常,
#并没有进入check_num装饰器进行参数校验,可见被装饰的函数add()的签名还是他本身
#如果直接构造装饰器,那么这里将会从check_num里面抛出异常,如下:
 
def check_num(func):
    def inner(*args,**kwargs):
        if len(args) != 2:
            raise Exception(&#39;Your args number is not two&#39;)
        else:
            return func(*args,**kwargs)
    return inner
 
@check_num
def add(x, y):
    return x + y
 
 
>>> add
<function inner at 0x0000000002E233C8>
>>>add(1,2,3)
Traceback (most recent call last):
  File "D:\PyCharm 5.0.4\helpers\pydev\pydevd.py", line 2411, in <module>
    globals = debugger.run(setup[&#39;file&#39;], None, None, is_module)
  File "D:\PyCharm 5.0.4\helpers\pydev\pydevd.py", line 1802, in run
    launch(file, globals, locals)  # execute the script
  File "E:/code/my_project/decorator/test3.py", line 14, in <module>
    print add(1,2,3)
  File "E:/code/my_project/decorator/test3.py", line 4, in inner
    raise Exception(&#39;Your args number is not two&#39;)
Exception: Your args number is not two

   

  decorator 模組就講到這裡,這個模組比較簡單,還有一些功能沒講到的查看源碼便一目了然,其原理都是利用python閉包實現的

包實現的

. func)裝飾器

   functools.wraps和decorator模組的功能是一樣的,都是為了解決被裝飾函數的簽名問題,這裡只對該種裝飾器構造方法列舉一個帶參數例子:

from decorator import decorator
 
 
def check(flag):
    @decorator
    def check_num(func, *args, **kwargs):
        if flag == &#39;false&#39;:
            print &#39;skip check !&#39;
            return func(*args,**kwargs)
        if len(args) != 2:
            raise Exception(&#39;Your args number is not two&#39;)
        else:
            return func(*args, **kwargs)
    return check_num
 
 
@check(&#39;false&#39;)
def add(x, y):
    return x + y
 
>>>add
<function add at 0x0000000002C53C18>
>>>add(1,2)
skip check !
3

   

  对比上面通过decorator模块装饰函数的例子,我们可以发现,用decorator装饰函数的代码更加简洁易懂,但是他们二者的执行效率谁更高呢?下面我们通过Timer来测试下:

from timeit import Timer
 
print Timer(&#39;add(1,2)&#39;,setup=&#39;from __main__ import add&#39;).timeit(100000)
 
#将该段代码 加在 之前的例子中
#这里打印的是运行100000次的时间

   

  functools.wraps装饰执行结果:

2.37299322602

   

  decorator模块装饰执行结果:

3.42141566059

   

  执行效率wraps略高,但是这里是执行了10万次他们之间的差距约为1秒,所以我个人还是比较青睐于用decorator模块装饰函数,毕竟看起来易懂,写法也较为简单!本文就将装饰器介绍到这里了,当然也没有说尽装饰器的妙用,比如:装饰类...其原理是用类来当做装饰器,类里面需要用到__call__方法,至于装饰类的用法感兴趣的朋友自行百度咯!

 


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn