Python函數裝飾器指南

Python 具備強大的功能和富有表現力的語法。我最喜歡的裝飾之一。在設計模式的上下文中,裝飾器動態更改方法或類功能,而沒必要直接使用子類。當您須要擴展功能,但不想修改原函數時,這是理想的選擇。咱們能夠在任何地方實現裝飾器模式,可是 Python 經過提供更具表現力的功能和語法來促進實現。html

在這篇文章中,將討論 Python 的函數裝飾器,並附帶一些澄清有關概念的示例。全部示例均適用 Python 2.7,但相同的概念應適用於Python 3,但語法有所更改。python

本質上,裝飾器充當包裝器,在目標函數執行以前和以後修改代碼的行爲,而無需修改函數自己,從而加強了原始功能,從而對其進行了裝飾。程序員

您須要瞭解的功能

在潛水以前,應先弄清一些先決條件。在 Python 中,函數是一等公民,它們是對象,這意味着咱們能夠用它們作不少有用的事情。設計模式

將函數分配給變量

def greet(name):
    return "hello "+name

greet_someone = greet
print(greet_someone("John"))

# 輸出: hello John

在其餘函數中定義函數

def greet(name):
    def get_message():
        return "Hello "

    result = get_message()+name
    return result

print(greet("John"))

# 輸出: Hello John

能夠將函數做爲參數傳遞給其餘函數

def greet(name):
   return "Hello " + name 

def call_func(func):
    other_name = "John"
    return func(other_name)  

print(call_func(greet))

# 輸出: Hello John

函數能夠返回其餘函數

換句話說, 函數生成其餘函數。閉包

def compose_greet_func():
    def get_message():
        return "Hello there!"

    return get_message

greet = compose_greet_func()
print(greet())

# 輸出: Hello there!

內部函數能夠訪問封閉範圍

更一般稱爲閉包。在構建裝飾器時會遇到的一種很是強大的模式。還要注意的另外一件事是,Python 只容許對外部做用域進行讀取訪問,而不是賦值。請注意,咱們如何修改上面的示例以從內部函數的封閉範圍中讀取「name」 參數並返回新函數。app

def compose_greet_func(name):
    def get_message():
        return "Hello there "+name+"!"

    return get_message

greet = compose_greet_func("John")
print(greet())

# 輸出: Hello there John!

裝飾者的組成

函數裝飾器只是現有函數的包裝器。綜上所述,咱們能夠構建一個裝飾器。在此示例中,咱們考慮一個函數,該函數經過p標籤包裝另外一個函數的字符串輸出。函數

def get_text(name):
   return "lorem ipsum, {0} dolor sit amet".format(name)

def p_decorate(func):
   def func_wrapper(name):
       return "<p>{0}</p>".format(func(name))
   return func_wrapper

my_get_text = p_decorate(get_text)

print(my_get_text("John"))

# 輸出: <p> lorem ipsum, John dolor sit amet</p>

那是咱們的第一個裝飾。一個將另外一個函數做爲參數的函數,將生成一個新函數,以擴展原始函數的功能,並返回生成的函數,以便咱們能夠在任何地方使用它。要讓 get_text 自己由 p_decorate 裝飾,咱們只需將 p_decorate 的結果再賦值給 get_text 便可。工具

get_text = p_decorate(get_text)

print(get_text("John"))

# 輸出:<p>lorem ipsum, John dolor sit amet</p>

還要注意的另外一點是,咱們的修飾函數帶有一個 name 參數。在裝飾器中咱們要作的就是讓 get_text 的包裝傳遞該參數。設計

Python的裝飾語法

Python經過一些語法糖使建立和使用裝飾器對程序員來講更乾淨,更友好。沒必要裝飾 get_text,get_text = p_decorator(get_text) 它有一個捷徑,即在要使用的函數以前提供裝飾函數的名稱便可。裝飾器的名稱應帶有@符號。調試

def p_decorate(func):
   def func_wrapper(name):
       return "<p>{0}</p>".format(func(name))
   return func_wrapper

@p_decorate
def get_text(name):
   return "lorem ipsum, {0} dolor sit amet".format(name)

print(get_text("John"))

# 輸出: <p>lorem ipsum, John dolor sit amet</p>

如今,讓咱們考慮咱們要用其餘2個函數來修飾 get_text 函數,以便在字符串輸出周圍包裝div和strong標籤。

def p_decorate(func):
   def func_wrapper(name):
       return "<p>{0}</p>".format(func(name))
   return func_wrapper

def strong_decorate(func):
    def func_wrapper(name):
        return "<strong>{0}</strong>".format(func(name))
    return func_wrapper

def div_decorate(func):
    def func_wrapper(name):
        return "<div>{0}</div>".format(func(name))
    return func_wrapper

使用基本方法,裝飾 get_text 將遵循如下步驟:

get_text = div_decorate(p_decorate(strong_decorate(get_text)))

使用 Python 的裝飾器語法,能夠用更具表達力的功能實現相同功能。

@div_decorate
@p_decorate
@strong_decorate
def get_text(name):
   return "lorem ipsum, {0} dolor sit amet".format(name)

print(get_text("John"))

# 輸出: <div><p><strong>lorem ipsum, John dolor sit amet</strong></p></div>

這裏要注意的一件事是設置裝飾器的順序很重要。若是以上示例中的順序不一樣,則輸出將不一樣。

裝飾方式

在 Python 中,方法是指望其第一個參數成爲對當前對象的引用的函數。咱們能夠以相同的方式爲方法構建裝飾器,同時在包裝函數中考慮自身

def p_decorate(func):
   def func_wrapper(self):
       return "<p>{0}</p>".format(func(self))
   return func_wrapper

class Person(object):
    def __init__(self):
        self.name = "John"
        self.family = "Doe"

    @p_decorate
    def get_fullname(self):
        return self.name+" "+self.family

my_person = Person()
print(my_person.get_fullname())

更好的方法是使裝飾器對函數和方法都有用。這能夠經過將*args 和 **kwargs做爲包裝器的參數來完成,而後它能夠接受任意數量的參數和關鍵字參數。

def p_decorate(func):
   def func_wrapper(*args, **kwargs):
       return "<p>{0}</p>".format(func(*args, **kwargs))
   return func_wrapper

class Person(object):
    def __init__(self):
        self.name = "John"
        self.family = "Doe"

    @p_decorate
    def get_fullname(self):
        return self.name+" "+self.family

my_person = Person()

print(my_person.get_fullname())

將參數傳遞給裝飾器

回顧上面的示例以前的示例,您會注意到示例中的裝飾器是多麼冗餘。3個裝飾器(div_decorate,p_decorate,strong_decorate)具備相同的功能,但用不一樣的標籤包裝字符串。咱們絕對能夠作得更好。爲何不爲將標籤包裝爲字符串的標籤提供更通用的實現?是的,請!

def tags(tag_name):
    def tags_decorator(func):
        def func_wrapper(name):
            return "<{0}>{1}</{0}>".format(tag_name, func(name))
        return func_wrapper
    return tags_decorator

@tags("p")
def get_text(name):
    return "Hello "+name

print(get_text("John"))

# 輸出 <p>Hello John</p>

在這種狀況下,須要作更多的工做。裝飾器指望接收一個函數做爲參數,這就是爲何咱們必須構建一個接受這些額外參數並動態生成裝飾器的緣由。在上面的示例tags,是咱們的裝飾器生成器。

調試裝飾功能

歸根結底,裝飾器只是包裝咱們的函數,以防調試出現問題,由於包裝器函數不攜帶原始函數的名稱,模塊和文檔字符串。基於上面的示例,若是咱們這樣作:

print(get_text.__name__)
# 輸出 func_wrapper

期待輸出get_text,然而,get_text__name____doc____module__屬性被包裝(func_wrapper)覆蓋。顯然,咱們能夠在func_wrapper中重置它們,可是Python提供了一種更好的方法。

救援工具

幸運的是,Python(從版本2.5開始)包括functools模塊,其中包含functools.wraps。Wraps 是一個修飾器,用於將包裝函數(func_wrapper)的屬性更新爲原始函數(get_text)的屬性。這就像經過@wraps(func)裝飾func_wrapper同樣簡單。這是更新的示例:

from functools import wraps

def tags(tag_name):
    def tags_decorator(func):
        @wraps(func)
        def func_wrapper(name):
            return "<{0}>{1}</{0}>".format(tag_name, func(name))
        return func_wrapper
    return tags_decorator

@tags("p")
def get_text(name):
    """returns some text"""
    return "Hello "+name

print(get_text.__name__) # get_text
print(get_text.__doc__) # returns some text
print(get_text.__module__) # __main__

您能夠從輸出中注意到,get_text 的屬性如今是正確的屬性。

裝飾器在哪裏使用

相對於您可使用裝飾器完成的工做量,本文中的示例很是簡單。它們能夠爲您的程序提供如此強大的功能。一般,裝飾器是擴展咱們不想修改的函數的行爲的理想選擇。有關有用的裝飾器的大量清單,建議您查看Python Decorator Library

相關文章
相關標籤/搜索