道一站 道一站
首页
  • 历史文明
  • 自我管理
  • 经济金融
  • 未来科技
  • 李笑来
  • 股票
  • 期货
  • 期权
  • 外盘
  • 量化
  • 区块
  • 认知
  • 数理
  • 收藏
  • 前端

    • JavaScript
    • TypeScript
  • 页面

    • HTML
    • CSS
  • 编程

    • C++
    • Python
    • Shell
    • 小程序开发
  • 笔记

    • 《Git》
    • 《JavaScript教程》
    • 《JavaScript高级程序设计》
  • 技术文档
  • Linux
  • Docker
  • GitHub
  • 博客搭建
  • 效用工具
  • 周易基础
  • 奇门遁甲
  • 收藏资源
  • 学习方法
  • 实用技巧
  • 友情链接
  • 分类
  • 标签
  • 归档
关于

daotoyi

静水流深
首页
  • 历史文明
  • 自我管理
  • 经济金融
  • 未来科技
  • 李笑来
  • 股票
  • 期货
  • 期权
  • 外盘
  • 量化
  • 区块
  • 认知
  • 数理
  • 收藏
  • 前端

    • JavaScript
    • TypeScript
  • 页面

    • HTML
    • CSS
  • 编程

    • C++
    • Python
    • Shell
    • 小程序开发
  • 笔记

    • 《Git》
    • 《JavaScript教程》
    • 《JavaScript高级程序设计》
  • 技术文档
  • Linux
  • Docker
  • GitHub
  • 博客搭建
  • 效用工具
  • 周易基础
  • 奇门遁甲
  • 收藏资源
  • 学习方法
  • 实用技巧
  • 友情链接
  • 分类
  • 标签
  • 归档
关于
  • C++

  • JavaScript

  • TypeScript

  • CSS

  • HTML

  • Python

    • Python 速查表
    • python 命名规则
    • Python 相关文件常见的后缀名
    • Python 中最常用的 5 种线程
    • Python 数据规范化(归一化)及Z-score标准化
    • Python 中的绝对导入和相对导入
    • Python 中有 3 个不可思议的返回功能
    • Python 技巧分享
    • PyQt5学习资料
    • PyQt5
    • PyQt5 QDockWidget
    • Python @1装饰器入门教程!
      • 1.1 函数中传递函数
      • 1.2 函数中定义函数
      • 1.3 函数返回函数
      • 1.4 函数内省
      • 2.1 第一个装饰器
      • 2.2 装饰器传参
    • Python @函数装饰器及用法
    • Python f-string 格式化字符串的 7 个层级
    • Python 中最常用的 5 种线程
    • Python 中有 3 个不可思议的返回功能
    • Python string去除(中文、英文、数字、标点符号)
  • Shell

  • Notes

  • 编程
  • Python
daotoyi
2023-10-07
目录

Python @1装饰器入门教程!

# 01有关函数的四个重要概念

相信你在大多数文章中,至少也能知道例如「装饰器是装饰函数」,「在不修改函数代码的情况下增加额外功能」等核心概念,但首先要知道为什么函数能够被装饰。

例如在《流畅的Python》一书中,讲到函数的一开始就提出了一个概念,函数是一等对象

正如书中所说,在Python中一个函数既可以作为参数被传递,也能作为另一个函数的返回值,这也是函数可以被装饰的关键,在介绍装饰器之前,下面有必要通过简单的代码对这段话做一个更直观的理解。

# 1.1 函数中传递函数

函数中传递函数意思就是可以将函数当作变量来使用,我们来看一个简单的示例。

在下面的代码中,func1是一个普通的函数,接受两个参数a,b并返回他们的和。func2不一样的地方在于多接收了一个func参数,这个func变量需要是一个函数

def func1(a,b): 
	print(f"函数 {func1.__name__} 正在执")
	return a + b

def func2(func,c,d): 
	print(f"函数 {func2.__name__} 正在执行") 
	return func(c,d)
1
2
3
4
5
6
7

现在让我们来执行func1

>>> func1(1,2)
函数 func1 正在执行
3
1
2
3

下面func1作为参数执行func2

>>> func2(func1,3,4)
函数 func2 正在执行
函数 func1 正在执行
7
1
2
3
4

可以看到,先执行func2,在func2接收到fun1后,再次执行func1并返回。注意这里的func1没有括号,它只不过是和a,b一样的参数被使用,理解这点后我们继续看下一个知识点。

# 1.2 函数中定义函数

在定义一个函数后,可以继续在函数内部定义新的函数。为了理解这点,我们来看下面简单的示例。

我们先定义了一个函数func1,并在func1中定义了func2,并在func1的内部调用了func2

def func1():
	print(f"函数 {func1.__name__} 正在执行")  

	def func2():  
		print(f"内部函数 {func2.__name__} 正在执行")  
	
func2()
1
2
3
4
5
6
7

现在执行func1与func2看看会发生什么

>>> func1()
函数 func1 正在执行内部函数
func2 正在执行

>>> func2()
------------------------------------------------Traceback (most recent call last)
----> 1 func2()
NameError: name 'func2' is not defined
1
2
3
4
5
6
7
8

可以看到,当执行func1时,会自动执行func2,但是如果单独执行func2,则提示未定义,说明func2只能在func1中被调用!

# 1.3 函数返回函数

最后是一个函数可以将另一个函数作为返回值返回的简单示例,在下面的代码中,我们先定义了一个外部函数func1(接受一个参数a),之后定义了一个内部函数func2(接受一个参数b)并返回a + b,最后将func2作为func1的返回值返回

def func1(a): 
	print(f"函数 {func1.__name__} 正在执行")  
	
	def func2(b):      
		print(f"函数 {func2.__name__} 正在执行")               
		return a + b     
	return func2
1
2
3
4
5
6
7

需要注意的是,这里返回的func2没有括号,代表返回的是func2的地址!

>>> func3 = func1(1)

>>> func3
函数 func1 正在执行
<function __main__.func1.<locals>.func2(b)

>>>> func3(2)
函数 func2 正在执行
3
1
2
3
4
5
6
7
8
9

从上面的运行结果可以看到,当执行func1(1)后,返回的是func2的地址,并赋给func3,之后执行func3(2)才真正执行了内部函数func2!

现在我们就解决了上一小节的问题「将内部函数func2单独拿出来用」!

# 1.4 函数内省

函数内省是相对来说比较好理解的一个概念,在Python中的意思就是我们可以访问函数的部分属性,例如print函数,可以使用dir函数来查看其全部属性

>>> dir(print)
['__call__', 
'__class__',
'__delattr__',
··· ··· '__subclasshook__',
'__text_signature__']
1
2
3
4
5
6

现在可以查看其对应的属性

>>> print.__name__
'print'
>>> print.__call__
<method-wrapper '__call__' of builtin_function_or_method object at 0x7fddb8056b80

>>>> print.__doc__
"print(value, ..., sep=' ', end='\\n', file=sys.stdout, flush=False)\n\nPrints the values to a stream, or to sys.stdout by default.\nOptional keyword arguments:\nfile:  a file-like object (stream); defaults to the current sys.stdout.\nsep:   string inserted between values, default a space.\nend:   string appended after the last value, default a newline.\nflush: whether to forcibly flush the stream."
1
2
3
4
5
6
7

函数内省了解到这个程度即可,我们会在2.3节再次提到这里的知识!

至此,我已经将接触装饰器之前必须要吃透的知识点介绍完毕,如果你觉得我讲解的不够清晰,可以查看任何其他教程或者书籍弄懂后再继续阅读。

# 02初识装饰器

现在终于可以来说说装饰器了,当然绝对不是直接告诉你一个写好的装饰器,而是我们一点一点去写一个简单的装饰器。

# 2.1 第一个装饰器

在下面的代码中,我们先定义了一个函数first_decorator,该函数接受函数为参数(如果不理解请查看本文 1.1 节),之后在内部定义了一个名为name_wrapper的内部函数(如果不理解请查看本文 1.2 节),最后返回以name_wrapper作为返回值(如果不理解请查看本文 1.3 节)

def first_decorator(func):
	def name_wrapper():     
		print(f"被装饰的函数 {func.__name__} 即将执行")       
		func()     
		print(f"被装饰的函数 {func.__name__} 执行完毕")          
	return name_wrapper
1
2
3
4
5
6

这个函数的功能是,在执行被接收函数前后分别打印一段话,所以我们要再定义一个函数来测试效果

def add(): 
	print("函数 add 正在执行 ")
1
2

这个fun1没有什么好说的,打印一段话。下面需要仔细看了,我们来执行这两段代码

>>> add = first_decorator(add)
>>> add()
被装饰的函数 add 即将执行函数 
add 正在执行 被装饰的函数 
add 执行完毕
1
2
3
4
5

正如我们预料的一样,在执行add前后都有一段提示,但是如果每次使用first_decorator功能都需要先将add传递,之后再调用,来回写好几遍,实在太麻烦了!

因此这完全有更Pythonic的写法,也就是我们常见的装饰器形式,使用语法糖@,例如上面的例子和下面的写法等价

@first_decorator
def add(): 
	print("函数 add 正在执行 ")
1
2
3

用@+装饰器函数名字放在需要被装饰函数的上方即可,现在直接调用add即可实现装饰器的功能!

>>> add()
被装饰的函数 add 即将执行函数
add 正在执行 被装饰的函数 
add 执行完毕
1
2
3
4

相信看到这里,你应该明白装饰器@如何工作的,至少你在未来看到@时需要想到类似等价于add = first_decorator(add)一样的作用!

# 2.2 装饰器传参

上面仅是最简单的装饰器示例,在实际使用时

很自然的想法那就是加上参数呗,改起来也不难

@first_decorator
def add(x,y):  
	print("函数 add 正在执行 ")   
	print(f"{x} + {y} 的结果为{x+y}")
1
2
3
4

让我们来测试一下

>>> add(1,2)
-----------------------------------------------
Traceback (most recent call last)<ipython-input-144-944f4051a32c> in <module>
----> 1 add(1,2)

TypeError: name_wrapper() takes 0 positional arguments but 2 were given
1
2
3
4
5
6

不出意外的报错了,虽然我们给被装饰的函数加上了参数,但是在装饰器的内部函数name_wrapper()执行时并没有参数!

因此我们之前的代码可以这么改,使用*args, **kwargs也是非常常见的用法

def first_decorator(func):  
	def name_wrapper(*args, **kwargs):  
		print(f"被装饰的函数 {func.__name__} 即将执行")    	    func(*args, **kwargs)     
		print(f"被装饰的函数 {func.__name__} 执行完毕")          
	return name_wrapper
1
2
3
4
5

现在我们再次使用这个装饰器即可返回我们预测的结果!

@first_decorator
def add(x,y):  
	print("函数 add 正在执行 ") 
	print(f"{x} + {y} 的结果为 {x+y}")

>>> add(1,2)
被装饰的函数 add 即将执行函数 
add 正在执行
1 + 2 的结果为 3被装饰的函数
add 执行完毕
1
2
3
4
5
6
7
8
9
10

# 03functools

本文的最后,还需要简单介绍一下在写装饰器时常用的functools模块。

还记得1.4节的函数内省相关知识吗?我们可以打印一个函数指向的内存地址或者名字等其他属性。

还是上面用到的add函数,我们都知道虽然被装饰了,但是功能上没有任何变化,依旧是计算两个数字的和,但是真的没有任何变化吗?下面让我们来观察一下

def add(x,y): 
	print("函数 add 正在执行 ")   
	print(f"{x} + {y} 的结果为{x+y}") 
	
>>> print(add)<function add at 0x7fddb9dd41f0>>>>print(add.__name__)add@first_decorator

def add(x,y): 
	print("函数 add 正在执行 ")   
	print(f"{x} + {y} 的结果为{x+y}")    
	
>>> print(add)
<function first_decorator.<locals>.name_wrapper at 0x7fddb9dd4e50

>>>> print(add.__name__)
name_wrapper
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

可以看到,被装饰后,虽然功能上没有变化,但是它指向的是装饰器所定义的内部函数!这并不是我们希望看到的,比如若不同函数被两个装饰器装饰时则会出现一样的函数名!

幸运的是Python中的functools库可以轻松解决这个问题,只需要加上一行简单的代码就可以搞定!

import functools
def first_decorator(func):   
	@functools.wraps(func) 
	def name_wrapper(*args, **kwargs): 
	
		print(f"被装饰的函数 {func.__name__} 即将执行")       
		func(*args, **kwargs)   
		print(f"被装饰的函数 {func.__name__} 执行完毕")  
		
	return name_wrapper
			
@first_decorator
def add(x,y):          
	print("函数 add 正在执行 ") 
	print(f"{x} + {y} 的结果为{x+y}")   
	
>>> print(add)
<function add at 0x7fddb9dd4e50

>>>> print(add.__name__)
add
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

可以看到此时函数名等函数属性均保留下来了,事实上@functools.wraps(func)通过functools.update_wrapper()将原函数中的部分内省属性固定,只传递部分关键参数来实现这个功能,感兴趣的读者可以自行进一步研究。

至此,我想你应该明白为什么需要现装饰器、什么是装饰器、以及如何写一个简单的装饰器,当你再次看到装饰器时,脑海中浮现的概念应该不仅仅是@。

- EOF -

上次更新: 2023/10/10, 14:48:21
PyQt5 QDockWidget
Python @函数装饰器及用法

← PyQt5 QDockWidget Python @函数装饰器及用法→

最近更新
01
置身事内
10-10
02
基础知识|八卦五行天干地支
10-08
03
基础知识|推算年月日时的干支
10-08
更多文章>
Theme by Vdoing | Copyright © 2021-2023 Daotoyi | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式