Python知識點:理解和使用裝飾器 @decorator

7bc450346fa4940569024f8178e24cb2ca6.jpg
Python的裝飾器(decorator)是一個很棒的機制,也是熟練運用Python的必殺技之一。裝飾器,顧名思義,就是用來裝飾的,它裝飾的是一個函數,保持被裝飾函數的原有功能,再裝飾上(添油加醋)一些其它功能,並返回帶有新增功能的函數對象,因此裝飾器本質上是一個返回函數對象的函數(確切的說,裝飾器應該是可調用對象,除了函數,類也能夠做爲裝飾器)。編程

在編程過程當中,咱們常常遇到這樣的場景:登陸校驗,權限校驗,日誌記錄等,這些功能代碼在各個環節均可能須要,但又十分雷同,經過裝飾器來抽象、剝離這部分代碼能夠很好解決這類場景。app

裝飾器是什麼?函數

要理解Python的裝飾器,首先咱們先理解一下Python的函數對象。咱們知道,在Python裏一切都是對象,函數也不例外,函數是第一類對象(first-class objects),它能夠賦值給變量,也能夠做爲list的元素,還能夠做爲參數傳遞給其它函數。工具

函數能夠被變量引用網站

定義一個簡單的函數:spa

def say_hi():
    print('Hi!')
say_hi()
# Output: Hi!

咱們能夠經過另一個變量say_hi2來引用say_hi函數:日誌

say_hi2 = say_hi
print(say_hi2)
# Output: <function say_hi at 0x7fed671c4378>

say_hi2()
# Output: Hi!

上面的語句中say_hi2 和 say_hi 指向了一樣的函數定義,兩者的執行結果也相同。code

函數能夠做爲參數傳遞給其它函數對象

def say_more(say_hi_func):
    print('More')
    say_hi_func()

say_more(say_hi)
# Output:
#     More
#     Hi

在上面的例子中,咱們把say_hi函數當作參數傳遞給say_more函數,say_hi 被變量 say_hi_func 引用。教程

函數能夠定義在其它函數內部

def say_hi():
    print('Hi!')
    def say_name():
        print('Tom')
    say_name()

say_hi()
# Output:
#     Hi!
#     Tom

say_name() # 報錯

上述代碼中,咱們在say_hi()函數內部定義了另一個函數say_name()。say_name()只在say_hi函數內部可見(即,它的做用域在say_hi函數內部),在say_hi外包調用時就會出錯。

函數能夠返回其它函數的引用

def say_hi():
    print('Hi!')
    def say_name():
        print('Tom')
    return say_name

say_name_func = say_hi()
# 打印Hi!,並返回say_name函數對象
# 並賦值給say_name_func

say_name_func()
# 打印 Tom

上面的例子,say_hi函數返回了其內部定義的函數say_name的引用。這樣在say_hi函數外部也可使用say_name函數了。

前面咱們理解了函數,這有助於咱們接下來弄明白裝飾器。

裝飾器(Decorator)

裝飾器是可調用對象(callable objects),它用來修改函數或類。
可調用對象就是能夠接受某些參數並返回某些對象的對象。Python裏的函數和類都是可調用對象。

函數裝飾器,就是接受函數做爲參數,並對函數參數作一些包裝,而後返回增長了包裝的函數,即生成了一個新函數。

讓咱們看看下面這個例子:

def decorator_func(some_func):
  # define another wrapper function which modifies some_func
  def wrapper_func():
    print("Wrapper function started")
    
    some_func()
    
    print("Wrapper function ended")
    
  return wrapper_func # Wrapper function add something to the passed function and decorator returns the wrapper function
    
def say_hello():
  print ("Hello")
  
say_hello = decorator_func(say_hello)

say_hello()

# Output:
#  Wrapper function started
#  Hello
#  Wrapper function ended

上面例子中,decorator_func 就是定義的裝飾器函數,它接受some_func做爲參數。它定義了一個wrapper_func函數,該函數調用了some_func但也增長了一些本身的代碼。

上面代碼中使用裝飾器的方法看起來有點複雜,其實真正的裝飾器的Python語法是這樣的:

裝飾器的Python語法

@decorator_func
def say_hi():
    print 'Hi!'

@ 符合是裝飾器的語法糖,在定義函數say_hi時使用,避免了再一次的賦值語句。
上面的語句等同於:

def say_hi():
    print 'Hi!'
say_hi = decorator_func(say_hi)

裝飾器的順序

@a
@b
@c
def foo():
    print('foo')

# 等同於:
foo = a(b(c(foo)))

帶參數函數的裝飾器

def decorator_func(some_func):
    def wrapper_func(*args, **kwargs):
        print("Wrapper function started")
        some_func(*args, **kwargs)
        print("Wrapper function ended")
    
    return wrapper_func

@decorator_func    
def say_hi(name):
    print ("Hi!" + name)

上面代碼中,say_hi函數帶有一個參數。一般狀況下,不一樣功能的函數能夠有不一樣類別、不一樣數量的參數,在寫wrapper_func的時候,咱們不肯定參數的名稱和數量,能夠經過args 和 *kwargs 來引用函數參數。

帶參數的裝飾器

不只被裝飾的函數能夠帶參數,裝飾器自己也能夠帶參數。參考下面的例子:

def use_logging(level):
    def decorator(func):
        def wrapper(*args, **kwargs):
            if level == "warn":
                logging.warn("%s is running" % func.__name__)
            return func(*args)
        return wrapper

    return decorator

@use_logging(level="warn")
def foo(name='foo'):
    print("i am %s" % name)

簡單來講,帶參數的裝飾器就是在沒有參數的裝飾器外面再嵌套一個參數的函數,該函數返回那個無參數裝飾器便可。

類做爲裝飾器

前面咱們提到裝飾器是可調用對象。在Python裏面,除了函數,類也是可調用對象。使用類裝飾器,優勢是靈活性大,高內聚,封裝性。經過實現類內部的__call__方法,當使用 @ 語法糖把裝飾器附加到函數上時,就會調用此方法。

class Foo(object):
    def __init__(self, func):
    self._func = func

def __call__(self):
    print ('class decorator runing')
    self._func()
    print ('class decorator ending')

@Foo
def say_hi():
    print('Hi!')

say_hi()
# Output:
# class decorator running
# Hi!
# class decorator ending

functools.wraps

使用裝飾器極大地複用了代碼,可是他有一個缺點就是原函數的元信息不見了,好比函數的docstring、__name__、參數列表,先看看下面例子:

def decorator_func(some_func):
    def wrapper_func(*args, **kwargs):
        print("Wrapper function started")
        some_func(*args, **kwargs)
        print("Wrapper function ended")
    
    return wrapper_func

@decorator_func    
def say_hi(name):
    '''Say hi to somebody'''
    print ("Hi!" + name)

print(say_hi.__name__)  # Output: wrapper_func
print(say_hi.__doc__)   # Output: None

能夠看到,say_hi函數被wrapper_func函數取代,它的__name__ 和 docstring 也天然是wrapper_func函數的了。
不過不用擔憂,Python有functools.wraps,wraps自己也是一個裝飾器,它的做用就是把原函數的元信息拷貝到裝飾器函數中,使得裝飾器函數也有和原函數同樣的元信息。

from functools import wraps
def decorator_func(some_func):
    @wraps(func)
    def wrapper_func(*args, **kwargs):
        print("Wrapper function started")
        some_func(*args, **kwargs)
        print("Wrapper function ended")
    
    return wrapper_func

@decorator_func    
def say_hi(name):
    '''Say hi to somebody'''
    print ("Hi!" + name)

print(say_hi.__name__)  # Output: say_hi
print(say_hi.__doc__)   # Output: Say hi to somebody

類的內置裝飾器

類屬性@property
靜態方法@staticmethod
類方法@classmethod

一般,咱們須要先實例化一個類的對象,再調用其方法。
若類的方法使用了@staticmethod或@classmethod,就能夠不須要實例化,直接類名.方法名()來調用。
從使用上來看,@staticmethod不須要指代自身對象的self或指代自身類的cls參數,就跟使用普通函數同樣。@classmethod不須要self參數,但第一個參數必須是指代自身類的cls參數。若是在@staticmethod中要調用到這個類的一些屬性方法,只能直接類名.屬性名,或類名.方法名的方式。
而@classmethod由於持有cls參數,能夠來調用類的屬性,類的方法,實例化對象等。

總結

經過認識Python的函數,咱們逐步弄清了裝飾器的前因後果。裝飾器是代碼複用的好工具,在編程過程當中能夠在適當的場景用多多使用。

我在個人我的博客「猿人學網站」和公衆號「猿人學Python」上寫Python教程,有興趣的能夠關注公衆號和網站。

相關文章
相關標籤/搜索