一篇文章理解Python 裝飾器(Decorator)

Python 裝飾器(Decorator)可以在不改變函數代碼和引用方式的情況下給函數增加新的功能。

提起Python 裝飾器,大家必定感覺特別神祕。其實裝飾器本質上就是一個函數(func_A),它接受被裝飾的函數(func_B)作爲參數,並在其內部嵌套了一個函數(func_C),func_C 對被裝飾的函數B進行封裝(通常是在func B的前後增加一些功能),然後返回這個包裝過的函數func_C。如下所示,我們看到的裝飾器通常是這樣的:

from functools import wraps

def func_A(func):
    @wraps(func)
    def func_C(*args,**kwargs):
        print 'do something before decoration'
        func(*args,**kwargs)
        print 'do something after decoration'
    return func_C

@func_A
def func_B():
    pass

那麼這個代碼裏的@wrap和@func_A是什麼意思呢,它們是怎麼來的呢?接下來我將一步步地帶大家理解。下文部分內容參考自https://www.runoob.com/w3cnote/python-func-decorators.html

1. Python中一切皆對象

在Python中,函數和類也是對象。具體體現在以下幾個方面:

(1)可以賦值給一個變量;

(2)可以添加到集合對象中;

(3)可以作爲參數傳遞給函數;

(4)可以當做函數返回值

# -*-coding:utf-8-*-
def hello(name='world'):
    return 'Hello, '+name
print hello()    # 調用該函數
# output: Hello, world

'''將函數賦值給變量'''
greet=hello    # 可將函數賦值給一個變量(注意這裏沒有加小括號)
print greet()    # 運行一下試試
# output: Hello, world

'''將函數添加到集合對象中'''
a_list=[]   # 定義一個集合對象
a_list.append(hello)    # 將函數添加到集合中
for item in a_list:
    print item
# output: <function hello at 0x000000000361E9E8>

'''作爲參數傳遞給函數'''
def print_type(var):    # 定義一個帶參數的函數
    print type(var)
print_type(hello)
# output: <type 'function'>

'''當做函數返回值'''
def return_a_func():
    print '返回一個函數:'
    return hello
my_hello=return_a_func()
print my_hello
# output: 返回一個函數:
# <function hello at 0x000000000331E9E8>

2. 在函數中再定義一個函數

剛纔是函數的基本知識了。更進一步的,在Python中我們可以在一個函數中定義另一個函數:

def hi(name="world"):
    print "now you are inside the hi() function"

    def greet():
        return "now you are in the greet() function"

    def welcome():
        return "now you are in the welcome() function"

    print greet()
    print welcome()
    print "now you are back in the hi() function"

hi()
#output:now you are inside the hi() function
#       now you are in the greet() function
#       now you are in the welcome() function
#       now you are back in the hi() function

# 上面展示了無論何時你調用hi(), greet()和welcome()將會同時被調用。

# 然後greet()和welcome()函數在hi()函數之外是不能訪問的,比如:
greet()
#outputs: NameError: name 'greet' is not defined

那現在我們知道了可以在函數中定義另外的函數。也就是說:我們可以創建嵌套的函數。現在需要再多學一點,就是函數也能返回函數。

3. 從函數中返回函數

其實並不需要再一個函數裏去執行另一個函數,我們也可以將其作爲輸出返回出來:

def hi(name="world"):
    def greet():
        return "now you are in the greet() function"

    def welcome():
        return "now you are in the welcome() function"

    if name == "world":
        return greet
    else:
        return welcome

a = hi()
print a
#outputs: <function greet at 0x7f2143c01500>

#上面清晰地展示了`a`現在指向到hi()函數中的greet()函數

#現在試試這個
print a()
#outputs: now you are in the greet() function

再次看看這個代碼。在 if/else 語句中我們返回 greet 和 welcome,而不是 greet() 和 welcome()。爲什麼那樣?這是因爲當你把一對小括號放在後面,這個函數就會執行;然而如果你不放括號在它後面,那它可以被到處傳遞,並且可以賦值給別的變量而不去執行它。 你明白了嗎?讓我再稍微多解釋點細節。

當我們寫下 a = hi(),hi() 會被執行,而由於 name 參數默認是 world,所以函數 greet 被返回了。如果我們把語句改爲 a = hi(name = "ali"),那麼 welcome 函數將被返回。我們還可以打印出 hi()(),這會輸出 now you are in the greet() function

4. 將函數作爲參數傳給另一個函數

def hi():
    return "hi world!"

def doSomethingBeforeHi(func):
    print "I am doing some boring work before executing hi()"
    print func()

doSomethingBeforeHi(hi)
#outputs:I am doing some boring work before executing hi()
#        hi world!

現在你已經具備所有必需知識,該進一步學習裝飾器是什麼了。裝飾器讓你在一個函數的前後去執行代碼。

5. 你的第一個裝飾器

在上一個例子裏,其實我們已經創建了一個裝飾器!現在我們修改下上面的裝飾器,並編寫一個稍微更有用點的程序:

def a_new_decorator(a_func):

    def wrapTheFunction():
        print "I am doing some boring work before executing a_func()"

        a_func()

        print "I am doing some boring work after executing a_func()"

    return wrapTheFunction

def a_function_requiring_decoration():
    print "I am the function which needs some decoration to remove my foul smell"

a_function_requiring_decoration()
#outputs: "I am the function which needs some decoration to remove my foul smell"

a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
#now a_function_requiring_decoration is wrapped by wrapTheFunction()

a_function_requiring_decoration()
#outputs:I am doing some boring work before executing a_func()
#        I am the function which needs some decoration to remove my foul smell
#        I am doing some boring work after executing a_func()

你看明白了嗎?我們剛剛應用了之前學習到的原理。這正是Python中裝飾器做的事情!它們封裝一個函數,並且用這樣或者那樣的方式來修改它的行爲。現在你也許疑惑,我們在代碼裏並沒有使用@符號?那只是一個簡短的方式來生成一個被裝飾的函數。這裏是我們如何使用@來運行之前的代碼:

@a_new_decorator
def a_function_requiring_decoration():
    """Hey you! Decorate me!"""
    print "I am the function which needs some decoration to remove my foul smell"

a_function_requiring_decoration()
#outputs: I am doing some boring work before executing a_func()
#         I am the function which needs some decoration to remove my foul smell
#         I am doing some boring work after executing a_func()

#the @a_new_decorator is just a short way of saying:
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)

希望你現在對Python裝飾器的工作原理有一個基本的理解。如果我們運行如下代碼會存在一個問題:

print a_function_requiring_decoration.__name__
# Output: wrapTheFunction

這並不是我們想要的!Ouput輸出應該是"a_function_requiring_decoration"。這裏的函數被warpTheFunction替代了。它重寫了我們函數的名字和註釋文檔(docstring)。幸運的是Python提供給我們一個簡單的函數來解決這個問題,那就是functools.wraps。我們使用functools.wraps修改下上一個例子:

from functools import wraps

def a_new_decorator(a_func):
    @wraps(a_func)
    def wrapTheFunction():
        print "I am doing some boring work before executing a_func()"
        a_func()
        print "I am doing some boring work after executing a_func()"
    return wrapTheFunction

@a_new_decorator
def a_function_requiring_decoration():
    """Hey yo! Decorate me!"""
    print "I am the function which needs some decoration to remove my foul smell"

print a_function_requiring_decoration.__name__
# Output: a_function_requiring_decoration

爲了將被裝飾函數的參數傳遞給裝飾器,加入了*args和**kwargs。藍本規範:

from functools import wraps
def decorator_name(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        if not can_run:
            return "Function will not run"
        return f(*args, **kwargs)
    return decorated

@decorator_name
def func():
    return("Function is running")

can_run = True
print func()
# Output: Function is running

can_run = False
print func()
# Output: Function will not run

注意:@wraps接受一個函數來進行裝飾,並加入了複製函數名稱、註釋文檔、參數列表等等的功能。這可以讓我們在裝飾器裏面訪問在裝飾之前的函數的屬性。

以上,就是Python裝飾器的來源。希望能夠對大家有所啓發。

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