闭包
定义
-
闭是封闭(函数中的函数),包是包含(该内部函数对外部函数作用域而非全局作用域变量的引用)
-
什么是闭包
- 内部函数对外部函数作用域里的变量的引用
- 函数内的属性,都是有生命周期(在函数执行期间的)
- 闭包内的闭包函数私有化了变量,完成了数据的封装,类似面向对象
引入
def foo():
print("in foo()")
def bar():
print("in bar()")
#1.直接运行内部函数报错
#bar()
#2.考虑先运行外部函数,再运行内部函数,依然会报错,外部函数在结束运行后资源又被释放
#foo()
#bar()
由于作用域的问题,函数内的属性都是有生命周期的,只有在函数执行期间
再考虑这段代码,只有调用foo()是,内部的print()及bar()才能存活
def foo():
print("in foo()")
def bar():
print("in bar()")
return bar #将bar函数体作为返回值返回
var = foo() #此时var等同与bar
var() #执行var()等同于执行bar()
#结果
in foo()
in bar()
-------------------------------------------------------
# 前面说,内部函数对外部函数作用域变量的引用
def foo():
a = 66
print("in foo()")
def bar(num):
print("in bar()")
print(a + num)
return bar
var = foo() #此时var就是var
var(22) #等价于直接执行bar(20)
print(a)
#结果
in foo()
Traceback (most recent call last):
in bar()
88
print(a) #脱离函数体,a为局部变量,报错
NameError: name 'a' is not defined
当bar()函数能够有连续操作时
li = [1, 2, 3, 4, 5]
def foo(obj):
print("foo:", obj)
def bar():
obj[0] += 1
print("bar:", obj)
return bar
var = foo(li)
var()
var()
#结果
foo: [1, 2, 3, 4, 5] #直接执行foo(li),并将bar返回给var
bar: [2, 2, 3, 4, 5] #第一次操作,obj[0]=1+1=2
bar: [3, 2, 3, 4, 5] #第二次操作,obj[0]=2+1=3
装饰器
引入
有时候需要在不影响原函数的情况下,对于函数继续功能扩展
实例
需要计算每个程序运行的时间
import time
def counts():
s=0
for i in range(100001):
s+=i
print('sum:%d'%s)
start =time.time()
counts()
end=time.time()
print('执行时间:',(end-start))
sum:5000050000
执行时间: 0.008973836898803711
成功的实现时间的计算,但是如果有成千上万个函数每个函数都怎么写一遍,非常麻烦
代码量也会凭空增加很多。
def use_time(func):
start = time.time()
func()
end = time.time()
print('执行时间:',(end-start))
经过修改,定义一个函数来实现时间计算的功能,简化了之前的操作,但是在使用时,还是需要将函数传入到时间计算函数中
def count_time(func):
def wrapper():
start=time.time()
func()
end=time.time()
print('执行时间:', (end - start))
return wrapper
a=count_time(counts)
a()
使用闭包,在使用时,让 counts 函数重新指向了 count_time 函数返回后的函数引用。这样在使用 counts 函数时,就和原来使用方式一样了。
import time
def count_time(func):
def wrapper():
start=time.time()
func()
end=time.time()
print('执行时间:', (end - start))
return wrapper
@count_time
def counts():
s=0
for i in range(1000001):
s+=i
print ('sum:%d'%s)
counts()
sum:500000500000
执行时间: 0.08475852012634277
使用装饰器,再调用counts时,同时调用了count_time,并且返回其中
在使用时,让counts函数重新指向count_time函数返回的函数调用。只有counts就和原来的使用方式一样了
实现就是采用装饰器
装饰器几种形式
1.无参数无返回值
def setFunc(func):
def wrapper():
print('start')
func()
print('end')
return wrapper
@setFunc
def show ():
print('in show')
show()
##结果:
start
in show
end
2.无参数有返回值
def setFunc(func):
def wrapper():
print('start')
func() #此时func()没有被任何其他接收,直接被释放,如果在此处return,则下面的end不会打印
print('end')
return func()#将func()返回,上面的func()没有用,可以直接省去
return wrapper
@setFunc
def show ():
return 'in show'
print(show())
start
end
in show #如果没有return func则为None
3.有参数有返回值
def setFunc(func):
def wrapper(s):
print('start')
func(s)
print('end')
return func('jc') #执行func(jc),返回None
return wrapper
@setFunc
def show (s):
print('hello %s'%s)
show('Bob')
#结果
start
hello Bob
end
hello jc
4.有参数有返回值
def setFunc(func):
def wrapper(a,b):
print('start')
print('end')
return func(a,b)
return wrapper
@setFunc
def add(x,y):
return x+y
print(add(5,6))
#结果
start
end
11
5.万能装饰器
根据被装饰函数的定义不同,分出了四种形式,
定义万能装饰器,能够满足任意形式的函数定义
def setFunc(func):
def wrapper(*args,**kwargs): #可变参数,接收不同参数类型
print('wrapper context.')
return func(*args,**kwargs)
return wrapper
@setFunc
def func(name,age,job='student'):
print(name,age,job)
func('bob',17)
@setFunc
def stp(a,b,*c,**d):
print(a,b)
print(c)
print(d)
stp('cy','clg',1991,11,11,tk='ul')
##结果:
wrapper context.
bob 17 student
wrapper context.
cy clg
(1991, 11, 11)
{'tk': 'ul'}
函数被多个装饰器装饰
一个函数在使用时,通过一个装饰器来拓展,可能并不能达到预期。
一个函数被多个装饰器所装饰。
def setFunc1(func):
def wrapper1(*args,**kwargs):
print('Wrapper Context 1 start.'.center(42,'-'))
func(*args,**kwargs)
print('Wrapper Context 1 end.'.center(42,'-'))
return wrapper1
def setFunc2 (func):
def wrapper2(*args, **kwargs):
print('Wrapper Context 2 start.'.center(42, '-'))
func(*args, **kwargs)
print('Wrapper Context 2 end.'.center(42, '-'))
return wrapper2
@setFunc2 #F
@setFunc1 #g
def show(*args,**kwargs): #f
print('show R.'.center(42))
show() #F(g(f))
#结果:
---------Wrapper Context 2 start.---------
---------Wrapper Context 1 start.---------
show R.
----------Wrapper Context 1 end.----------
----------Wrapper Context 2 end.----------
-
程序语句运行顺序:自下而上的修饰。先用F1装饰,再以结果为整体,用F2再装饰
-
函数调用来看:从内往外 。类似于复合函数
总结
这样实现的好处是,定义好了闭包函数后。只需要通过 @func 形式的装饰器语法,将 @func 加到要装饰的函数前即可。
使用者在使用时,根本不需要知道被装饰了。只需要知道原来的函数功能是什么即可。
这种不改变原有函数功能基础上,对函数进行扩展的形式,称为装饰器。
在执行 @func时 ,实际就是将 原函数传递到闭包中,然后原函数的引用指向闭包返回的装饰过的内部函数的引用。
回顾总结
- 函数可以像普通变量一样,作为函数的参数或者返回值进行传递。
- 函数的内部可以定义另外一个函数。目的,隐藏函数功能的实现。
- 闭包也是函数的一种定义形式
- 闭包定义的规则,再外部函数定义一个内部函数,内部函数使用外部函数的变量,并返回内部函数的引用
- python中,装饰器就是通过闭包来实现的
- 装饰器的作用是在不改变原有函数的基础上,为函数增加功能
- 装饰器的使用,通过
@装饰器函数名
的形式来给已有函数进行装饰,并添加功能 - 装饰器根据参数以及返回值可以分为四种,万能装饰器通过可变参数
(*args,**kwargs)
来实现 - 一个装饰器可以为多个函数提供装饰的功能,一个函数也可以使用多个装饰器
- 通过类可以实现装饰器:重写
__init__
和__call__
函数 - 类装饰器在装饰函数后,原来的引用不再是函数,而是装饰器的对象