python裝飾器

裝飾器的出現是廣大**的福音, 可以無需修改原有代碼結構,直接運行該代碼函數,而獲取該函數相關信息,如name,doc,run time等一系列信息,如下帶你進入裝飾器的世界.

1 入門

1.0 不帶參數裝飾器

  • Demo
from functools import wraps
def trace(func):
	# @wraps(func)
    def callf(*args, **kwargs):
        '''A wrapper function.'''
        print("Calling function: {}".format(func.__name__))
        res = func(*args, **kwargs)
        print("Return value: {}".format(res))
        return res
    return callf
    
@trace
def foo(x):
    '''Return value square.'''
    return x*x
if __name__ == "__main__":
    print(foo(3))
    print(foo.__doc__)# A wrapper function.
    print(foo.__name__)# callf
  • Result
Calling function: foo
Return value: 9
9
A wrapper function.
callf
  • Analysis
    (1) python3.x中使用@調用函數作爲裝飾器,其中trace爲裝飾器函數名,調用裝飾器和調用其他函數一樣,執行函數功能,該trace函數返回的是callf函數,因此執行callf函數功能;
    (2) 調用foo(3)即先執行callf函數,函數foo就是callf中的func函數,所以第一個結果爲Callling function:foo,由此可知func爲函數foo',第二個結果爲Return value: 9,第三個結果爲foo函數功能,輸出9.
    (3) foo函數爲裝飾器函數,由foo.__name__結果爲callf,裝飾器信息丟失,foo.__doc__結果A wrapper function可知.
    (4) 使用wrapper(func)可避免抓裝飾器信息丟失.
@trace 
def foo(x)
等價於:
trace(foo)

1.2 帶參數裝飾器

  • Demo
class logging(object):
    def __init__(self, level="INFO"):
        self.level = level
        
    def __call__(self, func):
        def wrapper(*args, **kwargs):
            print("[{level}]: enter function {func}()".format(level=self.level, func=func.__name__))
            func(*args, **kwargs)
#             return func(*args, **kwargs)
        return wrapper
@logging(level="INOF")
def say(x):
    print("hello {}".format(x))
if __name__ == "__main__":
    say("hahaha")
  • Result
[INOF]: enter function say()
hello hahaha
  • Analysis
    (1) 原理同上,只是帶參數的裝飾器使用類(class)的初始化提供參數輸入.
    (2) 通過對類中的函數進行重寫,如init初始化函數,call_{--}call_{--}調用函數,實現參數及函數使用.

2 提高

2.1 計時裝飾器

  • 普通函數plot_sin
def plot_sin():
    PI = np.pi
    x = np.linspace(-2*PI, 2*PI, 100)
    y = np.sin(x)
    plt.figure(figsize=(6, 6))
    plt.grid()
    plt.plot(x, y, 'r-', label="$y=sin(x)$")
    plt.legend()
    plt.show()

對於某一個函數,可能是自己寫的,也可能不是自己寫的,但是需要獲取程序運行時間,發現函數是一個月前甚至更久之前寫的,驚不驚喜,意不意外,此時裝飾器即可解決問題,無需修改源碼,直接寫裝飾器函數即可,代碼如下:

import numpy as np
import matplotlib.pyplot as plt
import time
 
def time_cal(func):
    def wrapper(*args, **kwargs):
        time_start = time.time()
        func()
        time_end = time.time()
        time_cost = time_end - time_start
        return time_cost
    return wrapper

@time_cal
def plot_sin():
    PI = np.pi
    x = np.linspace(-2*PI, 2*PI, 100)
    y = np.sin(x)
    plt.figure(figsize=(6, 6))
    plt.grid()
    plt.plot(x, y, 'r-', label="$y=sin(x)$")
    plt.legend()
    plt.show()
    
if __name__ == "__main__":
    print("plot_sin function cost time: {}s".format(plot_sin()))
  • Result
plot_sin function cost time: 0.1657559871673584s
  • Analysis
    (1) 已知函數plot_sin欲測試函數運行時間,但是對函數不熟悉,可直接使用裝飾器,不改變函數結構,在裝飾器中實現時間測試的功能.
    (2) 無需修改plot_sin函數結構,直接在裝飾器函數下面即可.

functools
閉包
裝飾器接口

2.2 wraps

from functools import wraps
def trace(func):
    @wraps(func)
    def callf(*args, **kwargs):
        '''A wrapper function.'''
        print("Calling function: {}".format(func.__name__))
        res = func(*args, **kwargs)
        print("Return value: {}".format(res))
        return res
    return callf
    
@trace
def foo(x):
    '''Return value square.'''
    return x*x
if __name__ == "__main__":
    print(foo(3))
    print(foo.__doc__)
    print(foo.__name__)
  • Result
Calling function: foo
Return value: 9
9
Return value square.
foo
  • Analysis
    (1) 使用wraps可避免裝飾器信息丟失.
    (2) foo.__name__輸出爲函數名foo.

2.3 裝飾器鏈

def wrapper_1(func):
    return lambda: "w1 "+func()+"w1 "
def wrapper_2(func):
    return lambda:"w2"+func()+"w2 "
@wrapper_1
@wrapper_2
def foo():
    return " hahah "
if __name__ == "__main__":
    print(foo())
  • Result
w1 w2 hahah w2 w1 
  • Analysis
    (1) 一個函數同時可由多個裝飾器使用,組成裝飾器鏈;
    (2) 執行順序是由近及遠,w1 w2 hahah w2 w1,先執行wrapper_2在執行wrapper_1.

2.4 內置裝飾器

2.4.1 @property

功能:將類的方法轉換爲同名稱的屬性,完成對屬性的增刪改,其中:
__intit__增加屬性;
@property獲取屬性;
@x.setter修改屬性;
@x.deleter刪除屬性;

  • Demo1
class Foo(object):
    def __init__(self):
        self._x = None
    @property
    def x(self):
        '''Get value.'''
        print("get")
        return self._x
    @x.setter
    def x(self, value):
        '''Set value.'''
        print("set")
        if value > 10:
            raise ValueError("invalid value")
        self._x = value
    @x.deleter
    def x(self):
        '''Delete value.'''
        print("delete attribute")
        del self._x
        
if __name__ == "__main__":
    foo = Foo()
    print(help(foo))
    '''set'''
    foo.x = 1
    '''get'''
    print("x value: {}".format(foo.x))
    '''delete'''
    del foo.x
    '''get'''
    print("x value: {}".format(foo.x))
  • Result
Help on Foo in module __main__ object:

class Foo(builtins.object)
 |  Methods defined here:
 |  
 |  __init__(self)
 |      Initialize self.  See help(type(self)) for accurate signature.
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)
 |  
 |  x
 |      Get value.

None
set
get
x value: 1
delete attribute
get

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-153-276bb444891e> in <module>
     30     del foo.x
     31     '''get'''
---> 32     print("x value: {}".format(foo.x))
     33 

<ipython-input-153-276bb444891e> in x(self)
      6         '''Get value.'''
      7         print("get")
----> 8         return self._x
      9     @x.setter
     10     def x(self, value):

AttributeError: 'Foo' object has no attribute '_x'
  • Analysis
    (1) help(instantiation)即help(foo)輸出類架構.
    (2) @property有四個參數,fget, fset, fdel, doc,其中:
序號 參數
1 fget 獲取屬性值
2 fset 設置屬性值
3 fdel 刪除屬性
4 doc 屬性描述

(3) @property默認屬性爲get,之後的二個爲get屬性的衍生,通過轉換後的屬性執行set,del.
(4) 執行del後,即刪除創建的屬性,因此不能獲取該屬性的值,所以報錯AttributeError: 'Foo' object has no attribute '_x'.

  • Demo2
class Foo(object):
    def __init__(self, name, score):
        self.name = name
        self._score = score
    @property
    def score(self):
        return self._score
    @score.setter
    def score(self, value):
        if value > 100:
            raise ValueError('invalid score')
        self.__score = value
    @score.deleter
    def score(self):
        del self._score

if __name__ == "__main__":            
            
    foo = Foo("hahah", 100)
    print(foo.score)
    foo.score = 1000
    foo.score
    foo.score
  • Reuslt
100
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-142-99037b9e42d8> in <module>
     19     foo = Foo("hahah", 100)
     20     print(foo.score)
---> 21     foo.score = 1000
     22     foo.score
     23     foo.score

<ipython-input-142-99037b9e42d8> in score(self, value)
      9     def score(self, value):
     10         if value > 100:
---> 11             raise ValueError('invalid score')
     12         self.__score = value
     13     @score.deleter

ValueError: invalid score
  • Analysis
    (1) 通過裝飾器@property可直接通過類的方法修改的私有屬性__score;
    (2) s.score替代s.__score.
    (3) 通過setter設定取值範圍,限制取值,超出範圍報錯;

2.4.2 classmethod

使用@classmethod將類作爲函數的第一參數,替代self傳遞當前類實例化對象.

  • Demo1
class Test(object):
    def foo(self, x):
        print("executing foo self:{}, x: {}".format(self, x))
    @classmethod
    def class_foo(cls, x):
        print("executing class_foo cls: {}, x: {}".format(cls, x))
    @staticmethod
    def static_foo(x):
        print("executing static_foo: {}".format(x))

t = Test()
t.foo(250)
t.class_foo(250)
t.static_foo(250)
Test.foo(t, 250)
Test.class_foo(250)
Test.static_foo(250)
  • Result
executing foo self:<__main__.Test object at 0x7fac7bfee518>, x: 250
executing class_foo cls: <class '__main__.Test'>, x: 250
executing static_foo: 250
executing foo self:<__main__.Test object at 0x7fac7bfee518>, x: 250
executing class_foo cls: <class '__main__.Test'>, x: 250
executing static_foo: 250
  • Analysis
    (1) self返回的是類Test的對象,cls返回的是類Test;
    (2) 使用@classmethod定義的函數可以通過類直接調用,無需實例化;
    (3) self傳遞的是類實例化的對象,cls傳遞的是類,因爲實例化t=Test()調用普通函數形式爲:t.foo,通過實例t調用函數,若使用類調用函數,需要將實例化的對象傳遞給self,如:Test.foo(t, 250);cls通過類調用函數:Test.class_foo;

  • Demo2

class DateOut(object):
    def __init__(self, year=0, month=0, day=0):
        self.year = year
        self.month = month
        self.day = day
    @classmethod
    def get_date(cls, data):
        year, month, day = map(int, data.split("-"))
        date = cls(year, month, day)
        return date
    def output_date(self):
        print("date: {}Y {}M {}D".format(self.year, self.month, self.day))
date = DateOut.get_date("2019-4-28")
print("date: {}".format(date))
date.output_date()
  • Result
date: <__main__.DateOut object at 0x7fac7bff3978>
date: 2019Y 4M 28D

3 總結

(1) 裝飾器分爲自定義裝飾器和內置裝飾器;
(2) 自定義裝飾器形成函數閉包,可以不修改函數獲取函數名稱,運行時間;
(3) 內置裝飾器常用的有@preperty和@classmethod;
(4) @property裝飾器可將函數作爲同名的屬性使用,完成增刪改查;
(5) @classmethod裝飾器可將類作爲第一個參數傳給類中的函數,這樣可以實現在類的函數中使用類的初始化功能;
(6) 類函數的第一個形參必須爲類的實例,用self表示類的實例化對象,作爲第一個形參,該參數約定俗成用self,也可以使用其他命名,爲了提高程序可讀性,公認使用self;


[參考文獻]
[1]https://www.cnblogs.com/Neeo/p/8371826.html
[2]https://www.cnblogs.com/cicaday/p/python-decorator.html
[3]https://www.cnblogs.com/huchong/p/7725564.html
[4]https://www.cnblogs.com/lianyingteng/p/7743876.html
[5]https://blog.csdn.net/kangkanglou/article/details/79076357
[6]https://docs.python.org/3/library/functions.html#property
[7]https://www.cnblogs.com/elie/p/5876210.html
[8]https://blog.csdn.net/test_xhz/article/details/82024838
[9]https://www.cnblogs.com/jessonluo/p/4717140.html
[10]http://www.cnblogs.com/chownjy/p/8663024.html


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章