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

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

文章目录

  • 在 Python 中使用装饰器的 7 个层次(7 Levels of Using Decorators in Python)
    • 导言
    • Level 0: 了解基本概念Basic Concepts和用法Usages
      • 什么是装饰器decorator?
      • 我们为什么需要装饰器decorator?
    • Level 1: Wrap a Function
    • Level 2: 将多个装饰器应用于一个函数
    • Level 3: 包装接收参数的函数
    • Level 4: 编写可接收参数的装饰器
    • Level 5: 保留原始函数Original Functions的元数据Metadata
    • 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。让我们来看一个简单的例子:

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。我们还可以如下应用装饰器:

get_title = add_author(get_title)

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

def add_author(func):print('Zhang San')return funcdef 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() 返回的标题?

当然,如下所示:

def add_things(func):def wrapper():title = func()new_title = title + ' !!!'return new_titlereturn 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集合到一个函数中。如何实现呢?

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

def add_author(func):def wrapper():author = 'Zhang San'return author + '\n' + func()return wrapperdef 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

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

# 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

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

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() 函数中添加一个参数,让它接收一个字符串作为标题,效果会更好。

def get_title(title):return title

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

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

def add_author(func):def wrapper(title):author = 'Zhang San'return author + '\n' + func(title)return wrapper@add_author
def get_title(title):return titleprint(get_title('Harry Potter'))
# Zhang San
# Harry Potter

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

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

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

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

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

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 titleprint(get_title('Harry Potter'))
# Zhang San
# Harry Potter@add_author
def get_many_title(t1, t2):return t1+'\n'+t2print(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。

def add_author_with_name(author):def add_author(func):def wrapper(*args, **kwargs):return author + '\n' + func(*args, **kwargs)return wrapperreturn add_author@add_author_with_name('J. K. Rowling')
def get_title(title):return titleprint(get_title('Harry Potter'))
# J. K. Rowling
# Harry Potter

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

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

def add_author_with_name(author):def add_author(func):def wrapper(*args, **kwargs):return author + '\n' + func(*args, **kwargs)return wrapperreturn add_authordef get_title(title):return titleget_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还有一个隐藏的副作用。让我们通过下面的例子来了解一下:

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 titleprint(get_title.__name__)
# wrapper
print(get_title.__doc__)
# None

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

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

from functools import wrapsdef 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 titleprint(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,也要一切从简。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/582746.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python新年炫酷烟花秀代码

新年马上就要到来,烟花秀必须得安排上! Pygame 绘制烟花的基本原理 1,发射阶段:在这一阶段烟花的形状是线性向上,通过设定一组大小不同、颜色不同的点来模拟“向上发射” 的运动运动,运动过程中 5个点被赋…

硅像素传感器文献调研(三)

写在前面: 引言:也是先总结前人的研究结果,重点论述其不足之处。 和该方向联系不大,但还是有值得学习的地方。逻辑很清晰,易读性很好。 1991年—场板半阻层 使用场板和半电阻层的高压平面器件 0.摘要 提出了一种…

线程基础知识(三)

前言 之前两篇文章介绍了线程的基本概念和锁的基本知识,本文主要是学习同步机制,包括使用synchronized关键字、ReentrantLock等,了解锁的种类,死锁、竞争条件等并发编程中常见的问题。 关键字synchronized synchronied关键字可…

Oracle中varchar2和nvarchar2的区别

Oracle中的varchar2和nvarchar2都是可变长度的字符数据类型,这意味着它们能够根据实际存储的数据长度来动态调整占用的空间。但它们之间有以下主要区别: 1. 字符编码和存储: - VARCHAR2:存储的是字节字符串,对字符…

出现频率高达80%的软件测试常见面试题合集(内附详细答案)

最近看到网上流传着各种面试经验及面试题,往往都是一大堆技术题目贴上去,但是没有答案。 为此我业余时间整理了这份软件测试基础常见的面试题及详细答案,望各路大牛发现不对的地方不吝赐教,留言即可。 01 软件测试理论部分 1.1…

Java线程池执行流程及参数详解

线程池的定义分为以下几个部分&#xff1a; public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {...}我们分别来看…

leaflet学习笔记-初始化vue项目(一)

leaflet简介 Leaflet是一款开源的轻量级交互式地图可视化JavaScript库&#xff0c;能够满足大多数开发者的地图可视化需求&#xff0c;其最早的版本大小仅仅38 KB。Leaflet能够在主流的计算机或移动设备上高效运行&#xff0c;其功能可通过插件进行扩展&#xff0c;拥有易于使用…

金睛云华斩获ISC2023数字安全创新能力百强 五项殊荣!

昨日&#xff0c;由北京市通州区人民政府指导&#xff1b;中关村科技园区通州园管理委员会、ISC平台主办的ISC2023数字安全创新能力百强颁奖典礼在京举行。金睛云华以卓越的产品创新实力&#xff0c;一举斩获网络与流量安全、威胁检测与响应、AI驱动安全、创新力十强、年度十强…

【JavaEE】多线程(6) -- 定时器的使用及实现

目录 定时器是什么 标准库中的定时器的使用 实现定时器 定时器是什么 Java中的定时器是一种机制&#xff0c;用于在预定时间执行某个任务。它允许开发人员在指定的时间间隔内重复执行任务&#xff0c;或在指定的延迟之后执行任务。定时器是Java提供的一种方便的工具&#xf…

本地部署 text-generation-webui

本地部署 text-generation-webui 0. 背景1. text-generation-webui 介绍2. 克隆代码3. 创建虚拟环境4. 安装 pytorch5. 安装 CUDA 运行时库6. 安装依赖库7. 启动 Web UI8. 访问 Web UI9. OpenAI 兼容 API 0. 背景 一直喜欢用 FastChat 本地部署大语言模型&#xff0c;今天试一…

web3方向产品调研

每次互联网形态的改变&#xff0c;都会对世界产生很大的影响&#xff0c;上一次对社会产生重大影响的互联网形态&#xff08;Web2.0&#xff09;催生了一批改变人类生活和信息交互方式的企业。 目录 概述DAO是什么&#xff1f;为什么我们需要DAO? 金融服务金融桥接及周边服务D…

Spring AOP—深入动态代理 万字详解(通俗易懂)

目录 一、前言 二、动态代理快速入门 1.为什么需要动态代理&#xff1f; : 2.动态代理使用案例&#xff1a; 3.动态代理的灵活性 : 三、深入动态代理 1.需求 : 2.实现 : 2.1 接口和实现类 2.2 提供代理对象的类 2.3 测试类 3.引出AOP : 四、总结 一、前言 第四节内容&…

Mybatis行为配置之Ⅱ—结果相关配置项说明

专栏精选 引入Mybatis Mybatis的快速入门 Mybatis的增删改查扩展功能说明 mapper映射的参数和结果 Mybatis复杂类型的结果映射 Mybatis基于注解的结果映射 Mybatis枚举类型处理和类型处理器 再谈动态SQL 文章目录 专栏精选引言摘要正文autoMappingBehaviorautoMappingU…

基于遗传算法的航线规划

MATLAB2016b可以正常运行 基于遗传算法的无人机航线规划资源-CSDN文库

代码随想录算法训练营Day13 |104.二叉树的最大深度、559.N叉树的最大深度、111.二叉树的最小深度、222.完全二叉树的节点个数

LeetCode 104 二叉树的最大深度 本题思想&#xff1a;用后序遍历的思想&#xff0c;先判断出左子树的深度&#xff0c;然后在右子树的深度。最后取大的那一个再加上根节点。就是最大深度。 class Solution {public int maxDepth(TreeNode root) {if(root null){return 0;}int…

(13)Linux 进程的优先级、进程的切换以及环境变量等

前言&#xff1a;我们先讲解进程的优先级。然后讲解进程的切换&#xff0c;最后我们讲解环境变量&#xff0c;并且做一个 "让自己的可执行程序不带路径也能执行"的实践&#xff0c;讲解环境变量的到如何删除&#xff0c;最后再讲几个常见的环境变量。 一、进程优先级…

跟着LearnOpenGL学习10--基础光照

文章目录 一、前言二、环境光照三、漫反射光照3.1、法向量3.2、计算漫反射光照3.3、全部代码3.4、法线矩阵 四、镜面光照4.1、全部代码 一、前言 现实世界的光照是极其复杂的&#xff0c;而且会受到诸多因素的影响&#xff0c;这是我们有限的计算能力所无法模拟的。 因此Open…

使用Rust发送邮件

SMTP协议与MIME协议 SMTP&#xff08;简单邮件传输协议,Simple Mail Transfer Protocol&#xff09;是一种用于发送和接收电子邮件的互联网标准通信协议。它定义了电子邮件服务器如何相互发送、接收和中继邮件。SMTP 通常用于发送邮件&#xff0c;而邮件的接收通常由 POP&#…

响应式绑定<a-textarea>的内容

项目中的 <a-textarea>组件需要自动填入下方数据表的物品名称数量单位&#xff0c;效果如下&#xff1a; 尝试 <a-textarea>{{插值}}</a-textarea>&#xff0c;实现不了&#xff0c;问ai得知需要使用v-decorator 指令的initialValue 属性 问&#xff1a; 如何…

js数组的方法

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、push()&&pop() unshift()&&shift()1、push()&#xff1a;向数组的末尾添加一个或多个元素&#xff0c;并返回新的长度2、pop()&#xff1a;删除…