学会使用装饰器之前,首先要明白什么是闭包函数
1.闭包
(1)什么是闭包?
- 闭包:内部函数对外部函数作用域里变量的引用
- 闭包函数必须满足两个条件:1.函数内部定义的函数 2.包含对外部作用域而非全局作用域的引用
这个概念略微有一点官方,不太好理解,接下来我们用示例来说明:
示例一:以下仅仅在函数内部定义了一个函数,但并非闭包函数.

示例二:以下在函数内部定义了一个函数,而且还引用了一个外部变量x,那么这个是闭包函数么?答案:不是

在回头来看看对闭包函数的定义,是不是两条都满足?聪明的你,一定发现不满足第二条.对,这里的变量x,是属于全局变量,而非外部作用于域的变量。再来看看下面例子:
示例三:显然,该例满足闭包函数的条件。现在,你应该清楚,作为一个闭包函数,必须得满足上述的两个条件,缺一不可。但是,一般情况下,我们都会给闭包函数返回一个值.这里先不说为什么.在接下来的内容中,你会看到这个返回值的用途.

(2)现在我们来抽象的定义一下闭包函数。它是函数和与其相关的引用环境组合而成的实体。在实现深约束时,需要创建一个能显式表示引用环境的东西,并将它与相关的子程序捆绑在一起,这样捆绑起成为闭包。在上面实例中,我们可以发现,闭包函数,它必须包含自己的函数以及一个外部变量才能真正称得上是一个闭包函数。如果没有一个外部变量与其绑定,那么這个函数不能算得上是闭包函数。
那么怎么知道一个闭包函数有多少个外部引用变量呢?看看下面代码

结果返回两个对象,这表明,在inner内部,引用了两个外部局部变量。如果引用的是非局部变量,那么这里输出的为None.
闭包函数的特点:1.自带作用域 2.延迟计算
(3)那么闭包函数有什么作用呢?我们清楚的知道,闭包函数在定义时,一定会绑定一个外部环境。這个整体才能算的上是一个闭包函数,那么我们可以利用这个绑定特性,来完成某些特殊的功能。
示例: 根据传入的URL,来下载页面源码

有人可以会说,这个不满足闭包函数的条件啊!我没有引用非全局的外部变量啊。其实并非如此,给,我们之前说过,只要在函数内部的变量都属于函数。那么我在index(url),这个url也属于函数内部,只不过我们省略一步而已,所以上面那个函数也是闭包函数。
2、装饰器——实现装饰器的过程中需要用到闭包函数
(1)装饰器作用及功能
装饰器实际上就是为了给某程序增添功能,但该程序已经上线或已经被使用,那么就不能大批量的修改源代码(即对修改是封闭的,对扩展是开放的),这样是不科学的也是不现实的,因为就产生了装饰器,使得其满足:
<1>不能修改被装饰的函数的源代码
<2>不能修改被装饰的函数的调用方式
<3>满足1、2的情况下给程序增添功能
那么根据需求,同时满足了这三点原则,这才是我们的目的。因为,下面我们从解决这三点原则入手来理解装饰器。
等等,我要在需求之前先说装饰器的原则组成:
- < 函数+实参高阶函数+返回值高阶函数+嵌套函数+语法糖 = 装饰器 >
简单举例:
例一:

例二:

上下图等效

(2)深刻理解装饰器的工作过程


(3)无参的装饰器
现有如下代码,我们需要计算一下代码执行的时间。
import time, random
def index():
time.sleep(random.randrange(1, 5))
print("welcome to index page")
index()
首先要理解Time模块的用法

根据装饰器的特点,我们不能对index()进行任何修改,而且调用方式也不能变。这时候,我们就可以使用装饰器来完成如上功能.

当然你也可以这么写

(4)有参的装饰器——同样是实现计算代码运行时间,我们再来做一遍
现在问题来了,我们如何接收参数呢?
我们以下图的代码为例,在其中做修改

用wrapper传递参数:

接受多个参数和关键字参数——(*args, ** kwargs)
import time
def decorator(func):
def warpper(*args,**kwargs):
print(time.time())
func(*args,**kwargs) ##此处接受的参数与wrapper一致
return warpper
@decorator
def f1(func_name):
print('This is a function ' + func_name)
@decorator
def f2(func_name1,func_name2):
print('This is a function ' + func_name1)
print('This is a function ' + func_name2)
@decorator
def f3(func_name1,func_name2,**kwargs):
print('This is a function ' + func_name1)
print('This is a function ' + func_name2)
print(kwargs)
f1('test')
f2('test1','test2')
f3('test1','test2',a=1,b=2,c='westos')

现在可以开始着手写代码了——装饰器实现一个函数计时器
我们将解决如下问题
问题1:被装饰的函数有返回值
问题2:如何保留被装饰函数的函数名和帮助信息文档
import time
import random
import string
import functools
li = [random.choice(string.ascii_letters) for i in range(10)]
print(li)
def decorator(fun):
"""这是一个装饰器"""
@functools.wraps(fun) ##这的参数要和装饰器的参数一致,没有这一行打印的是默认的文档,
# 就是wrapper函数的帮助文档
def wrapper(*args,**kwargs):
"""这是一个wrapper函数"""
t1 = time.time()
f = fun(*args,**kwargs)
t2 = time.time()
print('运行时间:%.8f' %(t2 - t1))
return f
return wrapper
@decorator
def con_add():
s = ''
for i in li:
s += (i + ',')
print(s)
@decorator
def join_add():
print(','.join(li))
@decorator
def fun_list(n):
"""这是fun_list函数"""
return [i * 2 for i in range(n)]
@decorator
def fun_map(n):
return list(map(lambda x:x*2,range(n)))
con_add()
join_add()
print(fun_list(10))
print(fun_map(10))
print(fun_list.__doc__)
print(fun_list.__name__)

对于有参的装饰器,如果上面的示例你还不明白,没事,我们再来一例!! ??
现在有这样的函数,我们需要写一个装饰器,完成以下功能:
当调用man函数时,打印:好好上班,你不用生娃,但必须做家务!
当调用woman函数时,打印:好好上班,你要生娃,家务男人去做!


(5)多个装饰器
现有如下代码:猜猜它们打印的顺序
def decorator_a(fun):
print('Get in decorator_a')
def inner_a(*args, **kwargs):
print('Get in inner_a')
res = fun(*args, **kwargs)
return res
return inner_a
def decorator_b(fun):
print('Get in decorator_b')
def inner_b(*args, **kwargs):
print('Get in inner_b')
res = fun(*args, **kwargs)
return res
return inner_b
@decorator_a
@decorator_b
def f(x):
print('Get in f')
return x * 2
f(2)

结果:

3.使用装饰器过程中出现的问题解决
问题1:被修饰的函数有返回值
被修饰的函数有返回值,需要在装饰器中将函数的返回值传给一个参数,并返回这个参数,否则返回值为None
比如你这么写:在被装饰函数中有返回(x + y),在装饰器中我们将被装饰函数的计算结果赋值给res,那么如果你不返回,会怎么样呢?


问题二:保留被装饰函数的函数名和帮助信息文档
<1> 导入functools包,加入@functools.wraps(fun)
<2> 帮助信息的写法:""“帮助信息”""
<3> print(函数名.doc) ##帮助信息文档
<4>print(函数名.name) ##函数名
例:

4.装饰器练习题
题1:
编写装饰器required_types, 条件如下:
1). 当装饰器为@required_types(int,float)确保函数接收到的
每一个参数都是int或者float类型;
2). 当装饰器为@required_types(list)确保函数接收到的每一>个参数都是list类型;
3). 当装饰器为@required_types(str,int)确保函数接收到的每
一个参数都是str或者int类型;
4). 如果参数不满足条件, 打印 TypeError:参数必须为xxxx类型
import functools
def required_types(*kinds):
def required(fun):
@functools.wraps(fun)
def wrapper(*args,**kwargs):
for i in args:
if not isinstance(i,kinds):
print('TypeError:参数必须为',kinds)
break
#raise TypeError('参数必须为%s,%s' %kinds)
else:
res = fun(*args,**kwargs)
return res
return wrapper
return required
@required_types(str,int)
def add(a,b):
return a + b
print(add(1.5,2.0))

题2:inspect.getcallargs的使用
import functools
import inspect
def is_admin(fun):
@functools.wraps(fun)
def wrapper(*args,**kwargs):
#inspect.getcallargs返回一个字典,key值是形参,value值
#是对应的实参{'name':'root'}
inspect_res = inspect.getcallargs(fun,*args,*kwargs)
print('inspect的返回值: %s' %inspect_res)
if inspect_res.get('name') == 'root':
res = fun(*args,**kwargs)
return res
else:
print('not root user!')
return wrapper
@is_admin
def add_user(name):
print('添加用户信息...')
def del_user(name):
print('删除用户信息...')
add_user('root')
题3:
定义装饰器admin:判断用户是否登陆成功
定义装饰器login:判断用户是否为超级用户root
先判断用户是否登陆成功,再判断书不是root用户
import functools
import inspect
def is_admin(fun):
@functools.wraps(fun)
def wrapper(*args,**kwargs):
#inspect.getcallargs返回一个字典,key值是形参,value值
#是对应的实参{'name':'root'}
inspect_res = inspect.getcallargs(fun,*args,*kwargs)
print('inspect的返回值: %s' %inspect_res)
if inspect_res.get('name') == 'root':
res = fun(*args,**kwargs)
return res
else:
print('not root user!')
return wrapper
login_session = ['root', 'redhat', 'westos']
def is_login(fun):
@functools.wraps(fun)
def wrapper(*args,**kwargs):
if args[0] in login_session:
res = fun(*args,**kwargs)
return res
else:
print('Error:%s未登录' %args[0])
return wrapper
@is_login
@is_admin
def add_student(name):
print('添加学生信息...')
add_student('linux')
本文详细介绍了Python中的闭包和装饰器的概念及其应用。首先解释了闭包的基本原理和特点,接着通过具体示例展示了装饰器的作用及其实现过程。最后通过一系列实践练习加深读者的理解。

886

被折叠的 条评论
为什么被折叠?



