(重点)闭包的定义形式:
(重点)装饰器的执行过程:
一、闭包和装饰器的描述:
闭包是指在一个函数中定义了一个另外一个函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用,这样就构成了一个闭包。 闭包的使用,可以隐藏内部函数的工作细节,只给外部使用者提供一个可以执行的内部函数的引用。
装饰器是用于拓展已有函数功能的一种函数,这个函数的特殊之处在于它的返回值也是一个函数,实际上就是利用闭包语法实现的。使用装饰器的好处就是在不用更改原函数的代码前提下给函数增加新的功能。
二、回顾函数的使用:
在使用函数时,函数可以传递参数,函数也可以返回数据。在传递和返回数据时,一般是传递返回的固定数据和代码执行的结果。
在Pyhton中,函数也是一个对象,在函数操作中,函数对象也可以当成一个参数或一个返回值进行返回。当程序内或程序外拿到参数的引用后就可以直接使用这个函数,(原理回想下深浅拷贝中的赋值)
三、闭包:
1.闭包:利用函数可以被传递和返回的特性,在开发过程中,可以隐藏更多的实现细节。
(重点)2.闭包的定义形式:
代码实现简单的闭包:
def callFunc():
n = 1
def show():
print('show: ', n)
return show
s = callFunc()
s()
# show() 因为 show 函数定义在 callFunc 内部,所以外部不可见,不能使用
代码改进后,去掉了全局变量的使用。而且将 show 函数封装在了 callFunc 函数内部,使外部不可见,不能使用 show 函数,隐藏了实现细节。
程序在执行时,callFunc 函数返回了内部定义的 show 函数,并且 在 show 函数内部使用了外部函数的变量。
在 show 函数返回时,保存了当前的执行环境,也就是会在 show 函数中使用的外部变量 n 。
因为 n 是一个 callFunc 函数中的局部变量,正常情况下 callFunc 函数执行结束后,n 就会被释放。
但是现在因为 callFunc 函数中返回了 show 函数,show 函数在外部还会再执行,所以程序会将 show 函数所需的执行环境保存下来。这种形式就是闭包。
简单总结一波,要实现闭包必须满足以下几点:
存在外函数和内函数;
内函数使用了外函数的函数变量;
外函数返回了内函数的引用;
闭包的案例:使用闭包完成棋子的移动:
代码实例:
#!/usr/bin/env python3
# coding=utf-8
"""闭包实现棋子移动"""
# 定义一个外部函数
def outer():
# 在外部函数中定义一个保存坐标的列表
position = [0,0]
# 定义一个内部函数,参数为移动方式和步长
# 移动方式为列表 [x,y] x,y分别只能取 -1,0,1三个值,表示反向,不动,正向
def inner(direction,step):
# 计算坐标值
position[0] = position[0] + direction[0] * step
position[1] = position[1] + direction[1] * step
# 返回移动后的坐标
return position
# 返回内部函数
return inner
if __name__ == '__main__':
# 获取内部函数
move = outer()
# 移动
print(move([1, 0], 10))
print(move([0, 1], 10))
print(move([-1, 0], 10))
运行结果:
[10, 0]
[10, 10]
[0, 10]
3.闭包的特点:
实现对函数有函数的隐藏
四、nonlocal 的使用:
如果在闭包的内部函数中直接使用外部函数的变量时,不需要任何操作,直接使用就可以了。
但是如果要修改外部变量的值,需要将变量声明为 nonlocal.
代码案例:
#!/usr/bin/env python3
# coding=utf-8
def callFunc():
m = 1
n = 2
def show():
print('show - m: ', m)
nonlocal n #如果不加会报错。
n *= 10
print('show - n: ', n)
return show
if __name__ == '__main__':
s = callFunc()
s()
nonlocal声明变量为非本地变量,如果确定在程序中要修改外部变量,那么就需要使用nonlocal声明一下要使用的外部函数变量。
五、小结:
闭包就是一个外部函数中定义一个内部函数,并且在内部函数中使用外部函数的变量,且外部函数返回内部函数的引用。
六、装饰器:
1. 什么是装饰器:
装饰器本身也是一个函数,这个函数以闭包的形式定义,作用是为现有存在的函数,在不改变函数本身及调用方式的基础上去增在使用装饰器时,在被修饰函数的前一行,使用 @装饰器名 的形式来装饰。
装饰器其实就是一个工厂函数,它接受一个函数为参数,然后返回一个新函数,其闭包中包含了原函数
2.使用场景:在不改变原有代码的情况下,统计实现一百万次累加的时间:
代码实现:
#!/usr/bin/env python3
# coding=utf-8
import time
# 定义一个计算运行时间的装饰器
def count_time(func):
def wrapper(): # wrapper:装饰
start_time = time.time()
func()
end_time = time.time()
print("一共执行了:", str(end_time - start_time), "秒")
return wrapper # 返回内函数的引用
# 定义完成100w次累加的函数
@count_time 这实际就相当于解决方法3中的 add_num = count_time(add_num)
def add_num():
sum_ = 0
for i in range(1000001):
sum_ += i
print("sum:", sum_)
if __name__ == '__main__':
add_num()
3.小结:
这样实现的好处是,定义好了闭包函数后。只需要通过 @xxx 形式的装饰器语法,将 @xxx 加到要装饰的函数前即可。使用者在使用时,根本不需要知道被装饰了。只需要知道原来的函数功能是什么即可。
这种不改变原有函数功能基础上,对函数进行扩展的形式,称为装饰器。在执行 @xxx 时 ,实际就是将 原函数传递到闭包中,然后原函数的引用指向闭包返回的装饰过的内部函数的引用。
七、装饰器的几种形式:
根据被装饰函数有无参数和有无返回值的排列组合,装饰器也有同函数的几种形式:
1.无参无返回值:
def setFunc(func):
def wrapper():
print('Start')
func()
print('End')
return wrapper
@setFunc
def show():
print('show')
show()
2.有参无返回值:
def setFunc(func):
def wrapper():
print('Start')
return func()
return wrapper
@setFunc # show = setFunc(show)
def show():
return 100
print(show() * 100)
3.无参有返回值:
def setFunc(func):
def wrapper(s):
print('Start')
func(s)
print('End')
return wrapper
@setFunc
def show(s):
print('Hello %s' % s)
show('Tom')
4.有参有返回值:
def setFunc(func):
def wrapper(x, y):
print('Start')
return func(x, y)
return wrapper
@setFunc
def myAdd(x, y):
return x + y
print(myAdd(1, 2))
八、万能装饰器:
根据被装饰函数的定义不同,上面细分了四种形式,那么能否利用学过的技术,实现一种适用于任何形式函数定义的装饰器呢?
答案是肯定的,通过可变参数和关键字参数来接收不同的参数类型。
代码实现:
#!/usr/bin/env python3
# coding=utf-8
def setFunc(func):
def wrapper(*args, **kwargs): # 接收不同的参数
print('wrapper context')
return func(*args, *kwargs) # 再原样传回给被装饰的函数
return wrapper
@setFunc
def show(name, age):
print(name, age)
if __name__ == '__main__':
show('tom', 12)
九、类实现装饰器形式:
通过类的定义也可以实现装饰器形式。
在类中通过使用 __init__ 和 __call__方法来实现
代码实现:
#!/usr/bin/env python3
# coding=utf-8
class Test(object):
# 通过初始化方法,将要被装饰的函数传递进来并作为属性记录
def __init__(self, func):
self.__func = func
# 重写__call__方法,实现装饰的内容
def __call__(self, *args, **kwargs):
print("装饰的内容1..")
return self.__func(*args, **kwargs)
@ Test # --> show = Test(show): Test通过重写call方法,使用Test类可以通过仿函数的形式调用
def show():
print("show run...")
if __name__ == '__main__':
# 类装饰器实际是通过call魔法方法实现的
show()
十、函数被多个装饰器所装饰:
个函数在使用时,通过一个装饰器来扩展,可能并不能完成达到预期。
Python 中允许一个函数被多个装饰器所装饰。
代码实现:
#!/usr/bin/env python3
# coding=utf-8
# 装饰器1
def setFunc1(func):
def wrapper1(*args, **kwargs):
print('Wrapper Context 1 Start...')
func(args, kwargs)
print('Wrapper Context 1 End...')
return wrapper1
# 装饰器2
def setFunc2(func):
def wrapper2(*args, **kwargs):
print('Wrapper Context 2 Start...')
func(args, kwargs)
print('Wrapper Context 2 End...')
return wrapper2
#一个函数被装饰了两次
@setFunc1
@setFunc2
def show(*args, **kwargs):
print('Show Run ...')
if __name__ == '__main__':
show()
运行结果:
Wrapper Context 1 Start...
Wrapper Context 2 Start...
Show Run ...
Wrapper Context 2 End...
Wrapper Context 1 End...
分析:
这个装饰器在时,过程是从下向上装饰,因为没有实际存在的函数,装饰器就没有意义。所以先装饰函数,然后再一层一层装饰。
@setFunc2 -> show = setFunc2(show) -> show = setFunc2.wrapper2 @setFunc1 -> show = setFunc1(setFunc2.wrapper2) -> show = setFunc1.wrapper1(setFunc2.wrapper2(show))
多层装饰器在实际使用时,并不多见,只需了解书写形式即可。
十一、装饰器传参:
扩展一个项目:
模拟miniWeb路由的实现:
代码实现:
#!/usr/bin/env python3
# coding=utf-8
"""装饰器传参"""
# 定义一个路由字典
route = {}
# 实现一个装饰器,让这个装饰器来实现自动将url和功能函数的匹配关系存在路由字典
def set_args(arg):
# 真正用来装饰接收的函数
def set_func(func):
# 真正的装饰函数
def wrapper(*args, **kwargs):
func()
# 将url和功能函数的对应关系保存到route字典中
route[arg] = wrapper
return wrapper
return set_func
# 定义功能函数
@set_args("login.html")
def login():
print("login Run..")
@set_args("nba.html")
def nba():
print("NBA Run..")
@set_args("news.html")
def news():
print("News Run..")
# 模拟运行的函数
def run(url):
# 通过传入的参数,也就是访问地址,到字典中去找到对应的功能函数
func = route[url]
# 调用函数
func()
if __name__ == '__main__':
print(route)
# 模拟请求
run("login.html")
run("nba.html")
运行结果:
{'login.html': .set_func..wrapper at 0x00000248A62EC158>, 'nba.html': .set_func..wrapper at 0x00000248A62EC268>, 'news.html': .set_func..wrapper at 0x00000248A62EC378>}
login Run..
NBA Run..
分析:
装饰阶段:
调用阶段:
程序在执行一开始,就初始化了装饰器工厂(即:带参的装饰器函数),将url和功能函数的键值关系保存到了route字典中。
明确一个知识点:装饰器工厂!!
十二、总结: