Python 简明教程 --- 22,Python 闭包与装饰器
微信公众号:码农充电站pro
个人主页:https://codeshellme.github.io
当你选择了一种语言,意味着你还选择了一组技术、一个社区。
目录
本节我们来介绍闭包
与装饰器
。
闭包与装饰器是函数的高级用法,其实在介绍完Python 函数我们就可以介绍本节的内容,但由于Python中的类
也可以用来实现装饰器,所以我们等到介绍完了Python 类再来统一介绍闭包与装饰器。
装饰器使用的是闭包的特性,我们先来介绍闭包,再来介绍装饰器。
1,什么是闭包
Python 的函数内部还允许嵌套函数
,也就是一个函数中还定义了另一个函数。如下:
def fun_1():
def fun_2():
return 'hello'
s = fun_2()
return s
s = fun_1()
print(s) # 'hello'
在上面的代码中,我们在函数fun_1
的内部又定义了一个函数fun_2
,这就是函数嵌套
。
我们在学习函数的时候,还知道,Python 函数可以作为函数参数
和函数返回值
。
因此,我们可以将上面代码中的函数fun_2
作为函数fun_1
的返回值,如下:
def fun_1():
def fun_2():
return 'hello'
return fun_2
此时,函数fun_1
返回了一个函数,我们这样使用fun_1
:
fun = fun_1() # fun 是一个函数
s = fun() # 调用函数 fun
print(s) # s 就是 'hello'
我们再来改进函数fun_1
,如下:
def fun_1(s):
s1 = 'hello ' + s
def fun_2():
return s1
return fun_2
上面的代码中,内部函数fun_2
返回了变量s1
,而s1
是函数fun_2
的外部变量
,这种内部函数
能够使用外部变量
,并且内部函数
作为外部函数
的返回值
,就是闭包
。
编写闭包时都有一定的套路,也就是,闭包需要有一个外部函数包含一个内部函数,并且外部函数的返回值是内部函数。
2,用闭包实现一个计数器
我们来实现一个计数器的功能,先写一个框架,如下:
def counter():
# 定义内部函数
def add_one():
pass
# 返回内部函数
return add_one
再来实现计数的功能,如下:
def counter():
# 用于计数
l = [0]
# 定义内部函数
def add_one():
l[0] += 1
return l[0] # 返回数字
# 返回内部函数
return add_one
上面的代码中,我们使用了一个列表l[0]
来记录累加数,在内部函数add_one
中对l[0]
进行累加。
我们这样使用这个计数器:
c = counter()
print(c()) # 1
print(c()) # 2
print(c()) # 3
我们还可以使这个计数器能够设置累加的初始值
,就是为counter
函数设置一个参数,如下:
def counter(start):
l = [start]
def add_one():
l[0] += 1
return l[0]
return add_one
这样我们就可以使用counter
来生成不同的累加器(从不同的初始值开始累加)。我们这样使用该计数器:
c1 = counter(1) # c1 从 1 开始累加
print(c1()) # 2
print(c1()) # 3
print(c1()) # 4
c5 = counter(5) # c5 从 5 开始累加
print(c5()) # 6
print(c5()) # 7
print(c5()) # 8
c1
从 1
开始累加,c5
从 5
开始累加,两个互不干扰。
3,什么是装饰器
装饰器
是闭包
的一种进阶应用。装饰器从字面上理解就是用来装饰
,包装
的。装饰器一般用来在不修改函数内部代码的情况下,为一个函数添加额外的新功能。
装饰器虽然功能强大,但也不是万能的,它也有自己适用场景:
- 缓存
- 身份认证
- 记录函数运行时间
- 输入的合理性判断
比如,我们有一个函数,如下:
def hello():
print('hello world.')
如果我们想计算这个函数的运行时间,最直接的想法就是修改该函数,如下:
import time
def hello():
s = time.time()
print('hello world.')
e = time.time()
print('fun:%s time used:%s' % (hello.__name__. e - s))
# 调用函数
hello()
其中,time
模块是Python 中的内置模块,用于时间相关计算。
每个函数都有一个__name__
属性,其值为函数的名字。不管我们是直接查看一个函数的__name__
属性,还是将一个函数赋值给一个变量后,再查看这个变量的__name__
属性,它们的值都是一样的(都是原来函数的名字):
print(hello.__name__) # hello
f = hello # 调用 f() 和 hello() 的效果是一样的
print(f.__name__) # hello
但是,如果我们要为很多的函数添加这样的功能,要是都使用这种办法,那会相当的麻烦,这时候使用装饰器就非常的合适。
最简单的装饰器
装饰器应用的就是闭包的特性,所以编写装饰器的套路与闭包是一样的,就是有一个外部函数和一个内部函数,外部函数的返回值是内部函数。
我们先编写一个框架:
def timer(func):
def wrapper():
pass
return wrapper
再来实现计时功能:
import time
def timer(func):
def wrapper():
s = time.time()
ret = func()
e = time.time()
print('fun:%s time used:%s' % (func.__name__, e - s))
return ret
return wrapper
def hello():
print('hello world.')
该装饰器的名字是timer
,其接受一个函数类型的参数func
,func
就是要修饰的函数。
func
的函数原型要与内部函数wrapper
的原型一致(这是固定的写法),即函数参数
相同,函数返回值
也相同。
英文
wrapper
就是装饰
的意思。
其实timer
就是一个高阶函数
,其参数是一个函数类型,返回值也是一个函数。我们可以这样使用timer
装饰器:
hello = timer(hello)
hello()
以上代码中,hello
函数作为参数传递给了timer
装饰器,返回结果用hello
变量接收,最后调用hello()
。这就是装饰器的原本用法。
只不过,Python 提供了一种语法糖
,使得装饰器的使用方法更加简单优雅
。如下:
@timer
def hello():
print('hello world.')
hello()
直接在原函数hello
的上方写一个语法糖@timer
,其实这个作用就相当于hello = timer(hello)
。
用类实现装饰器
在上面的代码中,是用函数
(也就是timer
函数)来实现的装饰器,我们也可以用类
来实现装饰器。
用类
实现装饰器,主要依赖的是__init__
方法和__call__
方法。
我们知道,实现
__call__
方法的类,其对象可以像函数一样被调用。
用类来实现timer
装饰器,如下:
import time
class timer:
def __init__(self, func):
self.func = func
def __call__(self):
s = time.time()
ret = self.func()
e = time.time()
print('fun:%s time used:%s' % (self.func.__name__, e - s))
return ret
@timer
def hello():
print('hello world.')
print(hello())
其中,构造方法__init__
接收一个函数类型的参数func
,然后,__call__
方法就相当于wrapper
函数。
用类实现的装饰器的使用方法,与用函数实现的装饰器的使用方法是一样的。
4,被修饰的函数带有参数
如果hello
函数带有参数,如下:
def hello(s):
print('hello %s.' % s)
那么装饰器应该像下面这样:
import time
def timer(func):
def wrapper(args):
s = time.time()
ret = func(args)
e = time.time()
print('fun:%s time used:%s' % (func.__name__, e - s))
return ret
return wrapper
@timer
def hello(s):
print('hello %s.' % s)
hello('python')
timer
函数的参数
依然是要被修饰的函数,wrapper
函数的原型与hello
函数保持一致。
用类来实现,如下:
import time
class timer:
def __init__(self, func):
self.func = func
def __call__(self, args):
s = time.time()
ret = self.func(args)
e = time.time()
print('fun:%s time used:%s' % (self.func.__name__, e - s))
return ret
@timer
def hello(s):
print('hello %s.' % s)
print(hello('python'))
不定长参数装饰器
如果hello
函数的参数是不定长
的,timer
应该是如下这样:
import time
def timer(func):
def wrapper(*args, **kw):
s = time.time()
ret = func(*args, **kw)
e = time.time()
print('fun:%s time used:%s' % (func.__name__, e - s))
return ret
return wrapper
@timer
def hello(s1, s2): # 带有两个参数
print('hello %s %s.' % (s1, s2))
@timer
def hello_java(): # 没有参数
print('hello java.')
hello('python2', 'python3')
hello_java()
这样的装饰器timer
,可以修饰带有任意参数的函数。
用类来实现,如下:
import time
class timer:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kw):
s = time.time()
ret = self.func(*args, **kw)
e = time.time()
print('fun:%s time used:%s' % (self.func.__name__, e - s))
return ret
@timer
def hello(s1, s2): # 带有两个参数
print('hello %s %s.' % (s1, s2))
@timer
def hello_java(): # 没有参数
print('hello java.')
hello('python2', 'python3')
hello_java()
5,装饰器带有参数
如果装饰器也需要带有参数,那么则需要在原来的timer
函数的外层再嵌套一层函数Timer
,Timer
也带有参数,如下:
import time
def Timer(flag):
def timer(func):
def wrapper(*args, **kw):
s = time.time()
ret = func(*args, **kw)
e = time.time()
print('flag:%s fun:%s time used:%s' % (flag, func.__name__, e - s))
return ret
return wrapper
return timer
@Timer(1)
def hello(s1, s2): # 带有两个参数
print('hello %s %s.' % (s1, s2))
@Timer(2)
def hello_java(): # 没有参数
print('hello java.')
hello('python2', 'python3')
hello_java()
从上面的代码中可以看到,timer
的结构没有改变,只是在wrapper
的内部使用了flag
变量,然后timer
的外层多了一层Timer
,Timer
的返回值是timer
,我们最终使用的装饰器是Timer
。
我们通过函数.__name__
来查看函数的__name__
值:
print(hello.__name__) # wrapper
print(hello_java.__name__) # wrapper
可以发现hello
和 hello_java
的__name__
值都是wrapper
(即内部函数wrapper
的名字),而不是hello
和 hello_java
,这并不符合我们的需要,因为我们的初衷只是想增加hello
与hello_java
的功能,但并不想改变它们的函数名字。
6,使用 @functools.wraps
我们可以使用functools
模块的wraps
装饰器来修饰wrapper
函数,以解决这个问题,如下:
import time
import functools
def Timer(flag):
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kw):
s = time.time()
ret = func(*args, **kw)
e = time.time()
print('flag:%s fun:%s time used:%s' % (flag, func.__name__, e - s))
return ret
return wrapper
return timer
@Timer(1)
def hello(s1, s2): # 带有两个参数
print('hello %s %s.' % (s1, s2))
@Timer(2)
def hello_java(): # 没有参数
print('hello java.')
此时,再查看hello
与 hello_java
的 __name__
值,分别是hello
和 hello_java
。
7,装饰器可以叠加使用
装饰器也可以叠加使用,如下:
@decorator1
@decorator2
@decorator3
def func():
...
上面代码的所用相当于:
decorator1(decorator2(decorator3(func)))
8,一个较通用的装饰器模板
编写装饰器有一定的套路,根据上文的介绍,我们可以归纳出一个较通用的装饰器模板:
def func_name(func_args):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
# 在这里可以使用func_args,*args,**kw
# 逻辑处理
...
ret = func(*args, **kw)
# 逻辑处理
...
return ret
return wrapper
return decorator
# 使用装饰器 func_name
@func_name(func_args)
def func_a(*args, **kw):
pass
在上面的模板中:
func_name
是装饰器的名字,该装饰器可以接收参数func_args
- 内部函数
decorator
的参数func
,是一个函数类型的参数,就是将来要修饰的函数 func
的参数列表可以是任意的,因为我们使用的是*args, **kw
- 内部函数
wrapper
的原型(即参数与返回值)要与 被修饰的函数func
保持统一 @functools.wraps
的作用是保留被装饰的原函数的一些元信息(比如__name__
属性)
与装饰器相关的模块有functools
和 wrapt
,可以使用这两个模块来优化完善你写的装饰器,感兴趣的小伙伴可以自己拓展学习。
(完。)
推荐阅读:
Python 简明教程 --- 17,Python 模块与包
Python 简明教程 --- 18,Python 面向对象
Python 简明教程 --- 19,Python 类与对象
Python 简明教程 --- 20,Python 类中的属性与方法
Python 简明教程 --- 21,Python 继承与多态
欢迎关注作者公众号,获取更多技术干货。
Python 简明教程 --- 22,Python 闭包与装饰器的更多相关文章
- Python的高级特性7:闭包和装饰器
本节跟第三节关系密切,最好放在一起来看:python的高级特性3:神奇的__call__与返回函数 一.闭包:闭包不好解释,只能先看下面这个例子: In [23]: def outer(part1): ...
- 13、python中的函数(闭包与装饰器)
一.嵌套函数 函数的内部又再定义另一个函数,这个函数就叫嵌套函数,里面含函数就叫内部函数. 示例: 二.返回函数 函数可以接收函数对象作为参数,同理函数也能返回一个函数对象作为返回值. 示例: 返回函 ...
- python 生成器,迭代器,闭包,装饰器
1.生成器,迭代器,闭包,装饰器的优点 生成器就是一类特殊的迭代器 迭代器的优点也即生成器的优点: 1.节约内存.python在使用生成器时对延迟操作提供了支持. 2.迭代到下一次的调用时,所使用的参 ...
- Python虚拟机函数机制之闭包和装饰器(七)
函数中局部变量的访问 在完成了对函数参数的剖析后,我们再来看看,在Python中,函数的局部变量时如何实现的.前面提到过,函数参数也是一种局部变量.所以,其实局部变量的实现机制与函数参数的实现机制是完 ...
- python基础(三)闭包与装饰器
闭包(closure): 内嵌函数通过调用外部嵌套函数作用域内的变量,则这个内嵌函数就是闭包. 闭包必须满足三个条件: 必须有一个内嵌函数 内嵌函数必须引用外部嵌套函数中的变量 外部函数的返回值必须是 ...
- Python基础之函数的闭包与装饰器的介绍
1.闭包的概念: 如果在一个函数中,定义了另外一个函数,并且那个函数使用了外面函数的变量,并且外面那个函数返回了里面这个函数的引用,那么称为里面的这个函数为闭包. 2.话不多说,以demo示例: de ...
- Python 简明教程 --- 23,Python 异常处理
微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 要么做第一个,要么做最好的一个. 目录 我们在编写程序时,总会不自觉的出现一些错误,比如逻辑错误,语 ...
- Python 简明教程 --- 24,Python 文件读写
微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 过去的代码都是未经测试的代码. 目录 无论是哪种编程语言,IO 操作都是非常重要的部分.I 即Inp ...
- Python 简明教程 --- 25,Python 目录操作
微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 做技术一定要一颗恒心,这样才不会半途而废. 目录 上一节我们介绍了文件相关的操作,本节我们来介绍目录 ...
随机推荐
- java实现蓝桥杯密码脱落
一 问题描述 X星球的考古学家发现了一批古代留下来的密码. 这些密码是由A.B.C.D 四种植物的种子串成的序列. 仔细分析发现,这些密码串当初应该是前后对称的(也就是我们说的镜像串). 由于年代久远 ...
- python—列表,元组,字典
——列表:(中括号括起来:逗号分隔每个元素:列表中的元素可以是数字,字符串,列表,布尔值等等) (列表元素可以被修改) list(类) (有序的) [1]索引取值:切片取值:for循环:whi ...
- .NET Web应用中为什么要使用async/await异步编程
前言 什么是async/await? await和async是.NET Framework4.5框架.C#5.0语法里面出现的技术,目的是用于简化异步编程模型. async和await的关系? asy ...
- 02-Python基础1
本节内容 列表.元组操作 字符串操作 字典操作 集合操作 文件操作 字符编码与转码 1. 列表.元组操作 列表是我们最以后最常用的数据类型之一,通过列表可以对数据实现最方便的存储.修改等操作 定义列表 ...
- JCreator配置的Java学习环境
绕不开的配置,很多东西需要它,论精力现在还折腾不来,可总不至于去见马克思的那一天才来啊,该来的就该来不躲避(Py及其Android.BigData都绕不开,总是触动着我)!不想那些庞大耗内存的Ecli ...
- 从0到70%:Chrome上位揭秘!
最近的数据显示,Chrome在2020年4月的市场份额达到了70%左右,把微软的Edge和Firefox远远甩在身后,毫无疑问,Chrome赢得了第二次游览器之战,成为新一代王者. Chrome的第一 ...
- 超详细实战教程丨多场景解析如何迁移Rancher Server
本文转自Rancher Labs 作者介绍 王海龙,Rancher中国社区技术经理,负责Rancher中国技术社区的维护和运营.拥有6年的云计算领域经验,经历了OpenStack到Kubernetes ...
- mall
https://github.com/macrozheng mall整合OSS实现文件上传:https://blog.csdn.net/zhenghongcs/article/details/9931 ...
- JVM 之 Linux定位CPU过高问题及优化
项目部署以后出行卡顿现象,所以对问题进行了排查,记录一下排查过程 (从CSDN编辑器贴过来的,图有水印) 1.找进程 top 可以发现,是Java进程导致的CPU过高,致使系统卡顿 2.找线程 ps ...
- IDEA之maven配置详解
这两天被maven配置搞得焦头烂额,前后忙活了三天才彻底搞定. 下面我总结一下配置的步骤. 步骤 1. 首先去maven官网去下载maven,http://maven.apache.org/ 这里教大 ...