当前位置: 首页 > news >正文

Python编程技巧 – 装饰器

Python编程技巧 – 装饰器

Python Programming Skills – Decorator

By Jackson@ML

0. 装饰器定义

在 Python编程语言中,有一种设计模式,称为装饰器(Decorator),它允许用户通过将函数包装在另一个函数中来修改函数的功能特性。

根据这个定义,该函数至少会由内外两层函数构成。外部函数称为装饰器,它将原始函数作为参数并返回它的修改过的版本。

1. 先决条件 – 嵌套函数

在了解装饰器之前,我们需要提前了解Python 函数相关的重要概念。

另外,请知晓,Python 中的所有内容(或者叫数据)都是对象,甚至函数也是对象。

1) 普通函数

先来回顾一下Python的普通自定义函数。

假设我们需要创建一个加法函数,传递两个参数 – 即被加数和加数,返回结果两个数相加的和,代码如下所示:

def add(x, y):return x + y

运行函数,如下命令:

add(3, 2)

得到结果为:

5

2) 嵌套函数

我们可以将一个函数包含在另一个函数中,称为嵌套函数

现在创建一个嵌套函数,假设定义外层函数为outer, 而内层函数为inner。代码如下所示:

def outer(x):def inner(y):return x + yreturn inneradd_five = outer(3)
result = add_five(4)
print(result)  # 打印输出为 7
3) 将函数作为参数传递

我们可以将一个函数作为参数传递给 Python 中的另一个函数。代码如下所示:

def add(x, y):return x + ydef calculate(func, x, y):return func(x, y)result = calculate(add, 4, 6)
print(result)  # 打印输出为 10

在上面的示例中,calculate()函数将函数func作为参数,另外还有两个参数x, y;在调用calculate()时,将add()函数作为参数进行了传递。

在calculate()函数里,参数func, x, y分别为add, 4和6。因此,func(x, y)变为add(4, 6), 并返回值10。

4) 将函数作为一个值返回

在 Python 中,我们也可以返回一个函数作为返回值。例如以下代码:

def greeting(name):def hello():return "Hello, " + name + "!"return hellogreet = greeting("China")
print(greet())  # 打印输出 "Hello, China!"

输出结果为:

Hello, China!

在上面的示例中,greeting()函数最后一行 - return hello 语句返回内部的 hello() 函数。此函数现在已分配给 greet 变量。

这就是为什么当我们将 greet() 作为函数调用时,我们会得到输出。

2. 装饰器

如前所述,Python 装饰器是一个函数,它接受一个函数并通过添加一些功能来返回它。

事实上,任何实现特殊 _ _ call _ _ ( )方法的对象都被称为可调用对象。因此,从最基本的意义上说,装饰器是一个返回可调用对象的可调用对象。

基本上,装饰器接受一个函数,添加一些功能并返回它。

def make_pretty(func):def inner():print("I got decorated")func()return innerdef ordinary():print("I am ordinary")

输出结果:

 I am ordinary

在这里,我们创建了两个函数:

  • ordinary() 打印 “I am ordinary”;
  • make_pretty() 将一个函数作为其参数,并具有一个名为 inner() 的嵌套函数,并返回内部函数。

我们通常调用 ordinary() 函数,因此我们得到输出“我是普通的”。现在,让我们使用 decorator 函数来调用它。

def make_pretty(func):# define the inner function def inner():# add some additional behavior to decorated functionprint("I got decorated")# call original functionfunc()# return the inner functionreturn inner# define ordinary function
def ordinary():print("I am ordinary")# decorate the ordinary function
decorated_func = make_pretty(ordinary)# call the decorated function
decorated_func()

输出结果为:

I got decorated
I am ordinary

在上面显示的示例中,make_pretty() 是一个装饰器。注意代码,

decorated_func = make_pretty(ordinary)

在这里,我们实际上是在调用 inner() 函数,我们在那里打印输出。

@ (带装饰器的符号)

Python 没有将函数调用分配给变量,而是提供了一种更优雅的方式来使用 @ 符号来实现此功能。例如:

def make_pretty(func):def inner():print("I got decorated")func()return inner@make_pretty
def ordinary():print("I am ordinary")ordinary()  

输出结果为:

I got decorated
I am ordinary

在这里,ordinary() 函数使用 @make_pretty 语法用 make_pretty() 装饰器进行修饰,这相当于调用 ordinary = make_pretty(ordinary)。

3. 使用参数装饰函数

上面的装饰器比较简单,它只适用于没有任何参数的函数。

如果我们有接受以下参数的函数会怎样,代码如下:

def divide(a, b):return a/b

此函数有两个参数,a 和 b。我们知道,如果我们将 b 传入为 0,它将给出一个错误。

现在,让我们制作一个装饰器来检查是否存在会导致错误的情况。

def smart_divide(func):def inner(a, b):print("I am going to divide", a, "and", b)if b == 0:print("Whoops! cannot divide")returnreturn func(a, b)return inner@smart_divide
def divide(a, b):print(a/b)divide(2,5)divide(2,0)

输出结果为:

I am going to divide 2 and 5
0.4
I am going to divide 2 and 0
Whoops! cannot divide

在这里,当我们使用参数 (2,5) 调用 divide() 函数时,会调用 smart_divide() 装饰器中定义的 inner() 函数。

这个 inner() 函数使用参数 2 和 5 调用原始 divide() 函数并返回结果,即 0.4。

同样,当我们使用参数 (2,0) 调用 divide() 函数时,inner() 函数会检查 b 是否等于 0,并在返回 None 之前打印错误消息。

4. 在 Python 中链接装饰器

用户可以在 Python 中链接多个装饰器。 为了在 Python 中链接装饰器,我们可以通过将多个装饰器一个接一个地放置来将多个装饰器应用于单个函数,首先应用最内部的装饰器。

def star(func):def inner(*args, **kwargs):print("*" * 15)func(*args, **kwargs)print("*" * 15)return innerdef percent(func):def inner(*args, **kwargs):print("%" * 15)func(*args, **kwargs)print("%" * 15)return inner@star
@percent
def printer(msg):print(msg)printer("Hello")

输出结果为:

***************
%%%%%%%%%%%%%%%
Hello
%%%%%%%%%%%%%%%
***************

下述的相关语法:

@star
@percent
def printer(msg):print(msg)

等同于以下代码:

def printer(msg):print(msg)
printer = star(percent(printer))

*注意:我们链接装饰器的顺序很重要。加入把@的顺序颠倒过来,如下代码所示:

@percent
@star
def printer(msg):print(msg)

那么,输出结果如下所示:

%%%%%%%%%%%%%%%
***************
Hello
***************
%%%%%%%%%%%%%%%

鸣谢

1. Python Tutorial
2. programiz.com

相关阅读
  1. Python开发实例 - Lambda表达式
  2. Python编程技巧 - 函数参数
  3. Python编程技巧 - 使用集合
  4. Python编程技巧 - 编写单行if条件语句
  5. Python编程技巧 - format格式化文本
  6. Python编程技巧 - 使用正则表达式
  7. Python编程技巧 - 单字符函数
  8. Python编程技巧 - 使用组合运算符
  9. Python编程技巧 - 异常处理
  10. Python编程技巧 - 迭代器
  11. Python编程技巧 - Lambda函数
  12. Python编程技巧 - 使用字典
  13. Python编程技巧 - 使用字符串
  14. Python编程技巧 - 对象和类
  15. Python编程技巧 - 使用列表
  16. Python编程技巧 - 转换二进制、八进制和十六进制的函数
  17. Python编程技巧 - 函数入门
  18. 2024最新版PyCharm安装使用指南
  19. 2024最新版Visual Studio Code安装使用指南

相关文章:

  • HCIA-HarmonyOS设备开发认证V2.0-IOT硬件子系统-GPIO
  • 深入理解java虚拟机---自动内存管理
  • 一.重新回炉Spring Framework: 理解Spring IoC
  • Python第十九章(模块)
  • PyCharm 新建目录 (directory or folder)
  • JavaScript 设计模式之组合模式
  • ubuntu 22.04 图文安装
  • Java使用Redis实现分页功能
  • 微服务中4种应对跨库Join的思路
  • 如何选择最适合的图纸加密软件?用户体验及性价比
  • 同一台宿主机上虚拟机CPU资源分配方式介绍
  • 【Redis实战】有MQ为啥不用?用Redis作消息队列!?Redis作消息队列使用方法及底层原理高级进阶
  • 剑指offer面试题16 反转链表
  • 【栈】150. 逆波兰表达式求值
  • 面向对象编程入门:掌握C++类的基础(1/3)
  • Java 最常见的 200+ 面试题:面试必备
  • JavaScript函数式编程(一)
  • JS基础篇--通过JS生成由字母与数字组合的随机字符串
  • leetcode388. Longest Absolute File Path
  • MySQL Access denied for user 'root'@'localhost' 解决方法
  • node 版本过低
  • OpenStack安装流程(juno版)- 添加网络服务(neutron)- controller节点
  • ReactNativeweexDeviceOne对比
  • webpack项目中使用grunt监听文件变动自动打包编译
  • 从重复到重用
  • 前嗅ForeSpider采集配置界面介绍
  • 区块链技术特点之去中心化特性
  • 如何解决微信端直接跳WAP端
  • 物联网链路协议
  • 一、python与pycharm的安装
  • scrapy中间件源码分析及常用中间件大全
  • ​​​​​​​Installing ROS on the Raspberry Pi
  • ​LeetCode解法汇总2808. 使循环数组所有元素相等的最少秒数
  • # Apache SeaTunnel 究竟是什么?
  • $.ajax()方法详解
  • (Matalb分类预测)GA-BP遗传算法优化BP神经网络的多维分类预测
  • (笔试题)合法字符串
  • (附源码)springboot人体健康检测微信小程序 毕业设计 012142
  • (算法)求1到1亿间的质数或素数
  • (原創) 未来三学期想要修的课 (日記)
  • (转)C#调用WebService 基础
  • (转)关于多人操作数据的处理策略
  • *上位机的定义
  • ./configure,make,make install的作用
  • .net core 6 使用注解自动注入实例,无需构造注入 autowrite4net
  • @manytomany 保存后数据被删除_[Windows] 数据恢复软件RStudio v8.14.179675 便携特别版...
  • @Service注解让spring找到你的Service bean
  • @SuppressWarnings(unchecked)代码的作用
  • [ 云计算 | Azure 实践 ] 在 Azure 门户中创建 VM 虚拟机并进行验证
  • [1525]字符统计2 (哈希)SDUT
  • [202209]mysql8.0 双主集群搭建 亲测可用
  • [Android]如何调试Native memory crash issue
  • [ANT] 项目中应用ANT
  • [Docker]四.Docker部署nodejs项目,部署Mysql,部署Redis,部署Mongodb
  • [Hadoop in China 2011] Hadoop之上 中国移动“大云”系统解析