闭包
闭包的理解
在一个函数中定义了另外一个函数,内函数使用了外函数的临时变量,外函数返回内函数的引用,那么**这个内部函数和它环境变量【外函数的临时变量】**合在一起,就形成了一个闭包。
和其他对象一样,函数对象也有其存活的范围,也就是函数对象的作用域。函数对象是使用def语句定义的,函数对象的作用域与def所在的层级相同。对于作用域以外的参数,我们称为该函数的环境变量。
举个栗子,假设我们需要通过定义一个函数,实现不同直线方程上点的求解,我们可以借助闭包实现:
def line(k,b):
def creat(x):
print(k*x+b) #传入外函数的变量
return creat #不带括号,返回内函数的引用;带括号就变成了函数的调用
line1 = line(2,3)
line1(3) ---> y = 2*3+3
line1(4) ---> y = 2*4+3
out:
9
11
我们通过变换参数k,b,就可以获得不同的直线表达函数,通过传入x可以求得不同直线方程上点的值。如果没有闭包,实现以上功能,我们需要每次创建直线函数的时候同时传入k,b,x。
闭包的特点(优势):
闭包可以提高代码的可复用性,传入的参数变少了,简化了代码的调用方式。普通函数调用结束后,保存的信息将被撤销,但闭包可以保存局部信息不被撤销,方便下次调用。
内函数中修改外函数的值
闭包可以将外层的参数在内存中进行保留,方便重复调用。如果想要在内函数中修改外函数的值,可以使用关键字 nonlocal。
def line(k, b):
def creat(x):
nonlocal k
a = 3
print(k * x + b)
return creat
line1 = line(2, 3)
line1(5)
out:
18
应用场景
1.装饰器
2.作为实参进行传递。闭包作为参数传递时,我们传入的是功能 + 数据
装饰器
概述:
将一个函数(类)的引用作为实参,传递到一个闭包的外部函数中去,**实现对原函数功能的扩展(**函数的调用形式不变)
类别
装饰器自身不带参数
1.对无参无返回值的函数进行装饰
2.对有参无返回值的函数进行装饰【装饰器中的内函数也需要接受原函数对应的参数】
3.对无参有返回值的函数进行装饰【装饰器中的内函数中对应的函数也需要加return】
4.通用装饰器【适应于以上所有的类型,以下对通用装饰器进行举例】
#通用装饰器示例
def set_func(func):
print("开始装饰...")
def call_func(*args,**kwargs):
#此处需要接收原函数对应的参数,一般使用不定长参数【不管原函数有没有参数都不会报错】
print(">>>函数执行前对应的操作<<<") #此处加入对原函数的一些功能拓展,执行原函数前都会先执行这些操作
return func(*args,**kwargs)
#此处传入原函数对应的参数,进行拆包操作;同理此处加上return语句【不管原函数有没有返回值都不会报错】
return call_func #[不加括号]
def test(*args,**kwargs):
print("传入元组参数:",args)
print("传入字典参数",kwargs)
return "OK"
#对test函数进行装饰,只需要执行如下操作:
test = set_func(test)
# 将一个新函数的引用赋值给tset, 这样test在调用时还是和以前一样,但是每次执行test函数前,都会先执行一些其它操作
a = (1,2)
b = {"姓名":"zhangsan","年龄":18}
print(test(a,b))
out:
开始装饰...
>>>函数执行前对应的操作<<<
传入元组参数: ((1, 2), {'姓名': 'zhangsan', '年龄': 18})
传入字典参数 {}
OK
通过以上示例,我们通过装饰器完成了函数test()功能的简单拓展。在实际应用中装饰器一般通过@进行实现,故以上示例可以改写为如下形式
def set_func(func):
print("开始装饰...")
def call_func(*args,**kwargs):
#此处需要接收原函数对应的参数,一般使用不定长参数【不管原函数有没有参数都不会报错】
print(">>>函数执行前对应的操作<<<") #此处加入对原函数的一些功能拓展,执行原函数前都会先执行这些操作
return func(*args,**kwargs)
#此处传入原函数对应的参数,进行拆包操作;同理此处加上return语句【不管原函数有没有返回值都不会报错】
return call_func #[不加括号]
@set_func #此处完成对test函数的装饰
def test(*args,**kwargs):
print("传入元组参数:",args)
print("传入字典参数",kwargs)
return "OK"
a = (1,2)
b = {"姓名":"zhangsan","年龄":18}
print(test(a,b))
在被装饰函数前加入@用来装饰的函数,即可完成装饰。在上例中在test()函数前加入@set_func,等效于执行以下操作:
test = set_func(test)
装饰器的作用可以理解为对原函数功能的一种拓展。通过装饰器的处理,函数的调用形式和以前一样,没有任何的差别,但是却能在执行函数前完成其它额外的操作。
自身传入参数的装饰器(采用三层函数定义装饰器)
- 传入不同的装饰函数,执行不同的功能
- 需要在原函数的基础上再加一层函数的实现
- 先调用外部函数,传入参数的同时,返回一个函数的引用,后面用返回的这个函数进行装饰
def login(text):
def decorator(func):
def wrapper(*args,**kargs):
print('%s---%s'(text,func.name))
return func(*args,**kargs)
return wrapper
return decorator
#等价于==>(login(text))(f)==>返回wrapper
@login('this is a parameter of decorator')
def f():
print('2019-06-13')
#等价于==>(login(text))(f)()==>调用wrapper()并返回f()f()
#输出:
#=>this is a parameter of decorator----f
#=>2019-06-13
上面的login是允许带参数的装饰器。它实际上是对原有装饰器的一个函数封装,并返回一个装饰器。我们可以将它理解为一个含有参数的闭包。当我们使用@login(‘this is a parameter of decorator’)调用的时候,Python能够发现这一层的封装,并把参数传递到装饰器的环境中。
类装饰器
相比函数装饰器,类装饰器具有灵活度大、封装性等优点。基于类装饰器的实现,必须实现__call__和__init__两个内置函数。
- __ init__:接收被装饰函数;
- __ call__:实现装饰逻辑,调用装饰函数。
当使用@形式将装饰器附加到函数上时,就会调用类中的__call__方法【被装饰函数调用时才执行】。
class Foo(object):
def __init__(self, func):
self._func=func
def __call__(self):
print(' class decorator runing')
self._func()
print(' class decorator ending')
@ Foo
def bar():
print(' bar')
bar()
out:
class decorator runing
bar
class decorator ending
多个装饰器对同一个函数进行装饰
装饰顺序:从下到上进行装饰,从上到下进行执行
@a
@b
@c
def f ():
pass
#等效于执行
f = a(b(c(f)))
格式
@fun_1 #用来装饰的函数
def fun_2(): #被装饰的函数
pass
所谓装饰:就是将@下面的函数(被装饰函数)作为实参,传入到@后面的函数(装饰函数)中,执行一遍并返回一个新的函数引用【这个函数一般是对原函数功能的拓展】
装饰时间
当python解释器执行到**@ *****代码时,就开始自动对函数(类)进行装饰。即在python代码解析阶段就已经完成了装饰,而不是等到调用的时候。
其它
- 同一个装饰器可以装饰多个函数;
- 可以对函数和类进行装饰;
- 可以用函数或类进行装饰