在 Python 中使用装饰器decorator的 7 个层次

在 Python 中使用装饰器的 7 个层次(7 Levels of Using Decorators in Python)

文章目录

  • [在 Python 中使用装饰器的 7 个层次(7 Levels of Using Decorators in Python)](#在 Python 中使用装饰器的 7 个层次(7 Levels of Using Decorators in Python))
    • 导言
    • [Level 0: 了解基本概念Basic Concepts和用法Usages](#Level 0: 了解基本概念Basic Concepts和用法Usages)
    • [Level 1: Wrap a Function](#Level 1: Wrap a Function)
    • [Level 2: 将多个装饰器应用于一个函数](#Level 2: 将多个装饰器应用于一个函数)
    • [Level 3: 包装接收参数的函数](#Level 3: 包装接收参数的函数)
    • [Level 4: 编写可接收参数的装饰器](#Level 4: 编写可接收参数的装饰器)
    • [Level 5: 保留原始函数Original Functions的元数据Metadata](#Level 5: 保留原始函数Original Functions的元数据Metadata)
    • [Level 6: 保持简单 --- 装饰器的设计哲学](#Level 6: 保持简单 — 装饰器的设计哲学)

掌握 Python 最神奇的功能

导言

在技术面试中,区分初级junior和高级senior Python 程序员的最简单、最快的方法就是让他或她编写装饰器decorator。因为掌握装饰器decorator这个最神奇的 Python 特性是 Python 开发者的一个里程碑milestone。

关于装饰器decorators有很多值得一提的提示tips和技巧tricks,但它们分散在不同的书籍或教程tutorials中,其中一些可能会让初学者更加困惑。这就是我写这篇文章的原因。

本文将分 7 个层次levels深入探讨 Python 装饰器decorators的所有核心概念core concepts、技术和用法。如果您理解了其中的一半,那么阅读包含装饰器的 Python 程序就会变得容易。如果您理解了全部,那么在 Python 中设计和编写装饰器将是小菜一碟piece of cake。

Level 0: 了解基本概念Basic Concepts和用法Usages

什么是装饰器decorator?

装饰器decorator只是 Python 的一个函数式编程特性functional programming feature。装饰器decorator接收一个可调用对象callable(函数function、方法method或类class),并返回一个可调用对象callable。让我们来看一个简单的例子:

python 复制代码
def add_author(func):
    print('Zhang San')
    return func
    
@add_author
def get_title():
    return '7 Levels of Using Decorators in Python'

print(get_title())
# Zhang San
# 7 Levels of Using Decorators in Python

上述 add_author(func) 函数是一个简单的装饰器decorator。它在接收函数运行前打印作者姓名。

如果函数需要使用该装饰器,我们可以在函数顶部的"@"符号后添加该装饰器。

"@"符号的使用乍一看让人一头雾水,但这只是 Python 的语法糖syntax sugar。我们还可以如下应用装饰器:

python 复制代码
get_title = add_author(get_title)

上述方法的结果与"@"方法完全相同:

python 复制代码
def add_author(func):
    print('Zhang San')
    return func

def get_title():
    return '7 Levels of Using Decorators in Python'

get_title = add_author(get_title)
print(get_title())
# Zhang San
# 7 Levels of Using Decorators in Python

因此,"@"方法一点也不吓人。它只是为我们提供了一个非常直观、优雅的选择,可以将装饰器应用到函数中。

我们为什么需要装饰器decorator?

装饰器decorator就像一个可重复使用的构件building block,我们可以在需要时将其应用到函数中,而无需编辑函数本身。

如前面的示例所示,只要我们需要在 get_title() 函数前打印作者姓名,就可以直接将该代码块( add_author 装饰器)装配到函数中。无需对 get_title() 函数进行任何修改。如果将来不需要打印作者姓名,只需删除或注释掉 get_title() 顶部的一行即可。

请牢记:"多次编辑一个函数容易出错。而在需要时组装装饰器则既优雅又不会出错。"

简而言之In a nutshell,装饰器decorator为我们提供了很大的灵活性,并将功能方法和主方法分离开来。许多 Python 内置模块和流行的第三方模块都使用了这一强大功能。

Level 1: Wrap a Function

在某些资料中,装饰器decorator也被称为包装器wrapper。因为它可以包装wrap一个函数function并改变其行为。

至于 level 0 中的示例,我们的装饰器decorator只是在 get_title() 执行之前打印了一些内容。我们能做得更多吗?例如更改 get_title() 返回的标题?

当然,如下所示:

python 复制代码
def add_things(func):
    def wrapper():
        title = func()
        new_title = title + ' !!!'
        return new_title
    return wrapper

@add_things
def get_title():
    return '7 Levels of Using Decorators in Python'

print(get_title())
# 7 Levels of Using Decorators in Python !!!

如上所示,我们定义了一个名为 wrapper() 的内部函数inner function,该函数封装了接收到的 func 并在其结果末尾添加了三个感叹号。

基本上,这个示例展示了在 Python 中编写装饰器decorator的通用模板common template。共有 3 个步骤:

  • 接收函数function作为参数argument
  • 定义一个包装函数wrapper function,对接收到的函数进行处理
  • 返回包装函数wrapper function

顺便提一下,在函数式编程functional programming中,我们将包含嵌套函数的装饰器命名为闭包closures。

到目前为止,我们已经了解了装饰器decorators的基本原理。我们还能编写一些简单的装饰器。

遗憾的是,实际需求可能非常复杂,上述基础知识不足以设计出一个强大的装饰器decorator。接下来将介绍更高级的装饰器技术。

Level 2: 将多个装饰器应用于一个函数

由于装饰器decorators被用作功能块functionality block,有时我们希望将许多装饰器decorators集合到一个函数中。如何实现呢?

非常简单,只需将所有需要的装饰器放在函数的顶部,如下所示:

python 复制代码
def add_author(func):
    def wrapper():
        author = 'Zhang San'
        return author + '\n' + func()
    return wrapper

def add_publication(func):
    def wrapper():
        pub = 'TechToFreedom'
        return pub + '\n' + func()
    return wrapper

@add_publication
@add_author
def get_title():
    return '7 Levels of Using Decorators in Python'

print(get_title())
# TechToFreedom
# Zhang San
# 7 Levels of Using Decorators in Python

我们应该注意的一个重要问题是所用装饰器的顺序。如果我们改变上述示例的顺序,结果就会不同:

python 复制代码
# Change the order of decorators
@add_author
@add_publication
def get_title():
    return '7 Levels of Using Decorators in Python'

print(get_title())
# Zhang San
# TechToFreedom
# 7 Levels of Using Decorators in Python

事实上,多个装饰器会从下到上逐层包裹函数。上面的代码与下面的代码相同:

python 复制代码
def get_title():
    return '7 Levels of Using Decorators in Python'

get_title = add_author(add_publication(get_title))

print(get_title())
# Zhang San
# TechToFreedom
# 7 Levels of Using Decorators in Python

Level 3: 包装接收参数的函数

我们之前的示例程序很好,但不够灵活。如果我们在 get_title() 函数中添加一个参数,让它接收一个字符串作为标题,效果会更好。

python 复制代码
def get_title(title):
    return title

但如何修改装饰器以适应这种变化呢?

我们可以让包装函数wrapper function帮助我们接收参数:

python 复制代码
def add_author(func):
    def wrapper(title):
        author = 'Zhang San'
        return author + '\n' + func(title)
    return wrapper

@add_author
def get_title(title):
    return title

print(get_title('Harry Potter'))
# Zhang San
# Harry Potter

上面的代码已经解决了这个问题,但并不是很强大。

如前所述,装饰器是一个构件building block,可以在需要时添加到其他函数中。但是,我们无法确保所有装配了 add_author 装饰器的函数都只有一个参数。

因此,我们的装饰器decorator是有限的,不能用于包含许多参数的函数。

我们是否需要编写许多类似的装饰器decorators,只是在包装器中使用不同的参数?

幸运的是,我们不必这样做。星号技巧asterisks technique可以让我们的生活更轻松:

python 复制代码
def add_author(func):
    def wrapper(*args, **kwargs):
        author = 'Zhang San'
        return author + '\n' + func(*args, **kwargs)
    return wrapper

@add_author
def get_title(title):
    return title

print(get_title('Harry Potter'))
# Zhang San
# Harry Potter

@add_author
def get_many_title(t1, t2):
    return t1+'\n'+t2

print(get_many_title('Harry Potter 1','Harry Potter 2'))
# Zhang San
# Harry Potter 1
# Harry Potter 2

如上所示,在星号asterisks的帮助下,我们的装饰器decorator可以装配到函数中,而无需考虑函数会收到多少个参数。

这种设计装饰器的方法既受欢迎又值得推荐,因为它能使装饰器更加灵活和强大。

Level 4: 编写可接收参数的装饰器

事实上,上一层的例子还有一个明显的错误:Zhang San is not the author of "Harry Potter"!

我们应该让我们的装饰器更加灵活,这样它就能接收到一个代表 "哈利-波特 "这一真正作者姓名的参数。

现在,事情变得有点复杂:目标函数target functions和装饰器decorator本身都应该接收参数arguments。实现这一任务的想法是在现有装饰器之外添加另一层layer。

python 复制代码
def add_author_with_name(author):
    def add_author(func):
        def wrapper(*args, **kwargs):
            return author + '\n' + func(*args, **kwargs)
        return wrapper
    return add_author

@add_author_with_name('J. K. Rowling')
def get_title(title):
    return title

print(get_title('Harry Potter'))
# J. K. Rowling
# Harry Potter

如上所述,我们只需在 add_author 装饰器decorator中添加一个外层outer layer 来接收参数argument。

上述程序与以下程序相同:

python 复制代码
def add_author_with_name(author):
    def add_author(func):
        def wrapper(*args, **kwargs):
            return author + '\n' + func(*args, **kwargs)
        return wrapper
    return add_author

def get_title(title):
    return title

get_title = add_author_with_name('J. K. Rowling')(get_title)

print(get_title('Harry Potter'))
# J. K. Rowling
# Harry Potter

Level 5: 保留原始函数Original Functions的元数据Metadata

到目前为止,我们已经设计出了一个非常灵活和强大的装饰器!但真正的高级工程师senior engineer会考虑到所有细节。实际上,装饰器函数decorator function还有一个隐藏的副作用。让我们通过下面的例子来了解一下:

python 复制代码
def add_author(func):
    def wrapper(*args, **kwargs):
        author = 'Zhang San'
        return author + '\n' + func(*args, **kwargs)
    return wrapper

@add_author
def get_title(title):
    """
    A func that receives and returns a title.
    """
    return title

print(get_title.__name__)
# wrapper
print(get_title.__doc__)
# None

上述结果与预期不符。 get_title 函数的名称name和 doc 也被包装了!这是装饰器decorators的副作用。

为了避免这种副作用,我们可以手动编写一些类似 wrapper.__name__ = get_title.__name__ 的代码。但还有一个更简单的方法:

python 复制代码
from functools import wraps

def add_author(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        author = 'Yang Zhou'
        return author + '\n' + func(*args, **kwargs)
    return wrapper

@add_author
def get_title(title):
    """
    A function that receives and returns a title string.
    """
    return title

print(get_title.__name__)
# get_title
print(get_title.__doc__)
# A function that receives and returns a title string.

如上所示,我们可以在 functools 模块中使用 wraps 装饰器,这将有助于我们保护元数据。在我看来,在每个封装函数中添加 wraps 装饰器是一种很好的做法,可以避免出现意想不到的结果。

Level 6: 保持简单 --- 装饰器的设计哲学

如果您达到了这一水平,那么您已经理解了,至少知道了 Python 装饰器decorators的所有核心技术core techniques。

最后但并非最不重要的一点是,在开始为项目设计装饰公司之前,有一个理念值得一提:

Keep it simple and stupid. --- A design principle noted by the U.S. Navy

保持简单和愚蠢 --- 美国海军指出的设计原则

装饰器decorator是一个优雅的工具elegant tool,可以帮助我们编写干净整洁的 Python 代码。但不要过度使用它或编写过于复杂的装饰器decorator。在我看来,一个具有三层函数three layers of functions的装饰器就足够了,将三个装饰器组装成一个函数也足够了。

俗话说As an old saying goes,过犹不及beyond is as wrong as falling short。我们应始终注意代码的可读性readability,即使使用的是复杂的功能complex feature,也要一切从简。

相关推荐
PythonFun2 小时前
Python批量下载PPT模块并实现自动解压
开发语言·python·powerpoint
炼丹师小米2 小时前
Ubuntu24.04.1系统下VideoMamba环境配置
python·环境配置·videomamba
GFCGUO2 小时前
ubuntu18.04运行OpenPCDet出现的问题
linux·python·学习·ubuntu·conda·pip
985小水博一枚呀4 小时前
【深度学习基础模型】神经图灵机(Neural Turing Machines, NTM)详细理解并附实现代码。
人工智能·python·rnn·深度学习·lstm·ntm
萧鼎5 小时前
Python调试技巧:高效定位与修复问题
服务器·开发语言·python
IFTICing6 小时前
【文献阅读】Attention Bottlenecks for Multimodal Fusion
人工智能·pytorch·python·神经网络·学习·模态融合
大神薯条老师6 小时前
Python从入门到高手4.3节-掌握跳转控制语句
后端·爬虫·python·深度学习·机器学习·数据分析
程序员爱德华6 小时前
Python环境安装教程
python
huanxiangcoco6 小时前
152. 乘积最大子数组
python·leetcode
萧鼎6 小时前
Python常见问题解答:从基础到进阶
开发语言·python·ajax