闭包

闭包的理解

在一个函数中定义了另外一个函数,内函数使用了外函数的临时变量,外函数返回内函数的引用,那么**这个内部函数和它环境变量【外函数的临时变量】**合在一起,就形成了一个闭包。

和其他对象一样,函数对象也有其存活的范围,也就是函数对象的作用域。函数对象是使用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代码解析阶段就已经完成了装饰,而不是等到调用的时候。

其它

  • 同一个装饰器可以装饰多个函数;
  • 可以对函数和类进行装饰;
  • 可以用函数或类进行装饰