Python函数装饰器指南

继承苔藓
• 阅读 3773

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

点赞
收藏
评论区
推荐文章
半臻 半臻
3年前
Python基础5——装饰器
13、装饰器其本质:在不需要做任何代码变动的前提下,增加额外的功能。装饰器返回的是一个函数对象。相当于把函数作为参数传递进去,然后对函数进行装饰其本质就是一个闭包作用:1.给原来的函数增加额外的功能2.把原来的函数作为参数传递进去13.1基本用法标准版的装饰器pythondefwrapper(func):func为原来的函数名defin
Irene181 Irene181
3年前
恶补了 Python 装饰器的六种写法,你随便问~
大家好,我是明哥。今天给大家分享一下关于装饰器的知识点,内容非常干,全程高能,认真吸收看完,一定会对装饰器有更深的理解。Hello,装饰器装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰
Karen110 Karen110
3年前
浅析装饰器的那些事儿
一、装饰器的简单定义外层函数返回里层函数的引用,里层函数引用外层函数的变量。二、装饰器的作用通俗来讲装饰器的作用就是在不改变已有函数代码前提下,为该函数增加新的功能。defrun():print('我会跑')fun()现在我想在原有函数的基础上新增一个功能:我会唱歌。这个时候利用装饰器则轻松可以帮我们实现这个功能。三、实例理解(1)不
Python进阶者 Python进阶者
4年前
浅析装饰器的那些事儿
一、装饰器的简单定义外层函数返回里层函数的引用,里层函数引用外层函数的变量。二、装饰器的作用通俗来讲装饰器的作用就是在不改变已有函数代码前提下,为该函数增加新的功能。defrun():print('我会跑')fun()现在我想在原有函数的基础上新增一个功能:我会唱歌。这个时候利用装饰器则轻松可以帮我们实现这个功能。三、实
Stella981 Stella981
3年前
Python进阶笔记(2)
'''装饰器装饰器(Decorators)是Python的一个重要部分。简单地说:他们是修改其他函数的功能的函数。他们有助于让我们的代码更简短。如果已经接触过FLASK的,想想路由功能。如果没有接触过FLASK的,建议学习下。''''''各种推导式(compre
Stella981 Stella981
3年前
Python 装饰器(Decorator)
Python 装饰器(Decorator)装饰模式有很多经典的使用场景,例如插入日志、性能测试、事务处理等等,有了装饰器,就可以提取大量函数中与本身功能无关的类似代码,从而达到代码重用的目的。下面就一步步看看Python中的装饰器。装饰器本身是一个Python函数,他可以让其他函数在不需要做任何代码变动
Stella981 Stella981
3年前
Python装饰器、内置函数之金兰契友
装饰器:装饰器的实质就是一个闭包,而闭包又是嵌套函数的一种。所以也可以理解装饰器是一种特殊的函数。因为程序一般都遵守开放封闭原则,软件在设计初期不可能把所有情况都想到,所以一般软件都支持功能上的扩展,而对源代码的修改是封闭的。开放封闭原则主要体现在两个方面:对功能扩展开放:意味着有新的需求或变化时,可以对现有代码进行扩展,以适
Stella981 Stella981
3年前
Python装饰器用法实例总结
一、装饰器是什么python的装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。简单的说装饰器就是一个用来返回函数的函数。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离
Stella981 Stella981
3年前
Python中函数装饰器及练习
1)装饰器的理解:1、作用:在不改变原函数的基础上,给函数增加功能   2、返回值:把一个函数当作参数,返回一个替代版的函数3、本质:返回函数的函数4、应用场景:计时器、记录日志、用户登陆认证、函数参数认证2)无参函数装饰器  实例:被装饰的函数没有参数     执行结果为:  
Stella981 Stella981
3年前
Django【十二】中间价
一前戏  我们在前面的课程中已经学会了给视图函数加装饰器来判断是用户是否登录,把没有登录的用户请求跳转到登录页面。我们通过给几个特定视图函数加装饰器实现了这个需求。但是以后添加的视图函数可能也需要加上装饰器,这样是不是稍微有点繁琐。  学完今天的内容之后呢,我们就可以用更适宜的方式来实现类似给所有请求都做相同操作的功能了二中间
3A网络 3A网络
2年前
Golang 常见设计模式之装饰模式
Golang常见设计模式之装饰模式想必只要是熟悉Python的同学对装饰模式一定不会陌生,这类Python从语法上原生支持的装饰器,大大提高了装饰模式在Python中的应用。尽管Go语言中装饰模式没有Python中应用的那么广泛,但是它也有其独到的地方。接下来就一起看下装饰模式在Go语言中的应用。简单装饰器我们通过一个简单的例子来
继承苔藓
继承苔藓
Lv1
独在异乡为异客,每逢佳节倍思亲。遥知兄弟登高处,遍插茱萸少一人。
文章
6
粉丝
0
获赞
0