乐趣区

Python函数装饰器指南

Python 具备弱小的性能和富裕表现力的语法。我最喜爱的装璜之一。在设计模式的上下文中,装璜器动静更改办法或类性能,而不用间接应用子类。当您须要扩大性能,但不想批改原函数时,这是现实的抉择。咱们能够在任何中央实现装璜器模式,然而 Python 通过提供更具表现力的性能和语法来促成实现。

在这篇文章中,将探讨 Python 的函数装璜器,并附带一些廓清无关概念的示例。所有示例均实用 Python 2.7,但雷同的概念应实用于 Python 3,但语法有所更改。

实质上,装璜器充当包装器,在指标函数执行之前和之后批改代码的行为,而无需批改函数自身,从而加强了原始性能,从而对其进行了装璜。

您须要理解的性能

在潜水之前,应先弄清一些先决条件。在 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”参数并返回新函数。

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

退出移动版