python 闭包 装饰器
闭包
在python中一切皆对象,这样就使得变量所拥有的属性,函数也同样拥有。这样我们就可以理解在函数内创建一个函数的行为是完全合法的。这种函数被叫做内嵌函数,这种函数只可以在外部函数的作用域内被正常调用,在外部函数的作用域之外调用会报错.而如果内部函数里引用了外部函数里定义的对象(甚至是外层之外,但不是全局变量),那么此时内部函数就被称为闭包函数。闭包函数所引用的外部定义的变量被叫做自由变量。闭包从语法上看非常简单,但是却有强大的作用。闭包可以将其自己的代码和作用域以及外部函数的作用结合在一起。
1. 函数引用
def test1():
print("--- in test1 func----")
# 调⽤函数
test1()
# 引⽤函数
ret = test1
print(id(ret))
print(id(test1))
#通过引⽤调⽤函数
ret()
运行结果:
>>>
--- in test1 func----
140212571149040
140212571149040
--- in test1 func----
2.什么是闭包
- 函数嵌套
- 往往内函数使用外函数的自由变量
- 外函数返回内函数的引用
# 定义一个函数 求 y = Kx + b 的值
# –*– coding: utf-8 –*–
# @Time : 2019/2/23 20:34
# @Author : Damon_duanlei
# @FileName : bibao_test.py
# @BlogsAddr : https://blog.youkuaiyun.com/Damon_duanlei
def line(k, b):
def creat_y(x):
print(k * x + b)
return creat_y
if __name__ == '__main__':
line_a = line(6, 2)
line_a(3)
line_b = line(2, 5)
line_b(5)
运行结果:
>>>
20
15
闭包执行原理:(上文代码为例)
- 代码从上向下执行,运行到第9行代码,发现定义了一个函数,系统不会进入函数执行,而是跳过此段函数代码继续向下执行第15行代码.
- 当系统运行到第 16 行代码, 发现此行代码调用上方定义的函数, 便带着参数 k = 6, b = 2跳到第 9 行执行函数.
- 外部函数(line)执行到第 10 行, 再次发现定义函数, 系统继续跳过定义函数阶段代码向下执行第12行 return 语句, 将内函数引用返回给第 16 行定义的变量.
- 系统向下执行第 17 行代码, 通过引用调用内函数 create_y ,执行过程中内函数未发现变量 k b 就会向上一层(外层)函数中寻找变量k和b , 找到后执行完内函数打印结果. 至此完成一个闭包过程
- 代码继续向下执行到第 18行, 如上流程在执行一次.
总结: 函数, 匿名函数, 闭包, 对象的区别
函数: 普通函数能够完成较为复杂的功能, 普通函数传递的是这个函数的引用,只有功能.
匿名函数: 匿名函数能够完成较为简单的功能, 匿名函数传递的是这个函数的引用,只有功能.
闭包: 闭包能够完成较为复杂的功能, 闭包传递的是这个比保重的函数以及数据,因此传递的事功能+数据
对象:对象能够完成最为复杂的功能,对象传递所有的属性和方法,但是占用较大资源
3 闭包结构中修改外部函数中的变量
类似于函数中修改全局变量使用globle ,在闭包中使用 nonlocal(python3) 可以修改外部函数中的变量
def counter(start=0):
def incr():
nonlocal start
start += 1
return start
return incr
c1 = counter(5)
print(c1())
print(c1())
c2 = counter(50)
print(c2())
print(c2())
print(c1())
print(c1())
print(c2())
print(c2())
装饰器 (decorator)
装饰器的核心: 将函数 A 的引用当做实参,传递到闭包 B 的外层变量中去, 当调用闭包的内函数时, 通过传递的引用调用A函数.
变现形式: 在不改变原函数的情况下, 对原函数的功能进行修改
实质: 是一个函数
参数: 是要装饰的函数名
返回: 装饰后的函数名
作用: 为已经存在的对象添加额外的功能
特点: 不需要对对象做任何的代码上的变动
1.装饰器的功能
- 引入日志
- 函数执行时间统计
- 执行函数前预处理
- 执行函数后清理功能
- 权限校验
- 缓存
2. 装饰器示例
装饰无参数的函数
# –*– coding: utf-8 –*–
# @Time : 2019/2/25 21:14
# @Author : Damon_duanlei
# @FileName : test02.py
# @BlogsAddr : https://blog.youkuaiyun.com/Damon_duanlei
import time
def timer(func):
def call_func():
t1 = time.time()
func()
t2 = time.time()
print(t2 - t1)
return call_func
@timer # 等价于 test = timer(test)
# 测试函数
def test():
print("开始执行test:")
for i in range(1000):
for j in range(1000):
pass
print("test执行结束")
if __name__ == '__main__':
test()
执行结果:
>>>
开始执行test:
test执行结束
0.014960050582885742
详解:
上面代码装饰器执行行为可理解成
test = timer(test)
# test 先作为参数赋值给 func 后, test 接收指向 timer 返回的 call_func
test()
# 调用 test(), 即等价调用 call_func
# 内部函数 call_func 被引⽤,所以外部函数的func变量(⾃由变量)并没有释
# func 里保存的事原 test 函数对象
2. 被装饰的函数有参数
# –*– coding: utf-8 –*–
# @Time : 2019/2/25 21:49
# @Author : Damon_duanlei
# @FileName : test03.py
# @BlogsAddr : https://blog.youkuaiyun.com/Damon_duanlei
def check(func):
def call_func(user_name):
print("校验权限1")
print("校验权限2")
func(user_name)
return call_func
@check # 等价于 test = timer(test)
# 测试函数
def test(name):
print("{} 正在执行程序".format(name))
if __name__ == '__main__':
test("臭臭")
3. 被装饰的函数有不定长参数
# –*– coding: utf-8 –*–
# @Time : 2019/2/25 21:58
# @Author : Damon_duanlei
# @FileName : test04.py
# @BlogsAddr : https://blog.youkuaiyun.com/Damon_duanlei
import time
import random
def timer(func):
def call_func(*args, **kwargs):
t1 = time.time()
func(*args, **kwargs)
t2 = time.time()
print(t2 - t1)
return call_func
@timer # 等价于 test = timer(test)
# 测试函数
def bubble_sort(sort_list, reverse):
if reverse == 1:
for i in range(len(sort_list) - 1):
for j in range(len(sort_list) - 1 - i):
if sort_list[j] > sort_list[j + 1]:
sort_list[j], sort_list[j + 1] = sort_list[j + 1], sort_list[j]
else:
for i in range(len(sort_list) - 1):
for j in range(len(sort_list) - 1 - i):
if sort_list[j] < sort_list[j + 1]:
sort_list[j], sort_list[j + 1] = sort_list[j + 1], sort_list[j]
if __name__ == '__main__':
sort_list = [random.randint(0, 10000) for i in range(10000)]
bubble_sort(sort_list, 2)
4. 装饰有 return 的函数
# –*– coding: utf-8 –*–
# @Time : 2019/2/25 21:58
# @Author : Damon_duanlei
# @FileName : test04.py
# @BlogsAddr : https://blog.youkuaiyun.com/Damon_duanlei
import time
import random
def timer(func):
def call_func(*args, **kwargs):
t1 = time.time()
number = func(*args, **kwargs)
t2 = time.time()
print("排序用时:{}".format(t2 - t1))
return number
return call_func
@timer # 等价于 test = timer(test)
# 测试函数
def test(num_a, num_b):
return num_a + num_b
if __name__ == '__main__':
num = test(11, 22)
print(num)
注意 : 一般情况下为了让装饰器更通用, 可以在内函数调用传递进来的装饰函数前加 return
5. 装饰器本身带参数, 在原有装饰器的基础上, 设置外部变量
from time import ctime, sleep
def timefun_arg(pre="hello"):
def timefun(func):
def wrapped_func():
print("%s called at %s %s" % (func.__name__, ctime(), pre))
return func()
return wrapped_func
return timefun
# 下⾯的装饰过程
# 1. 调⽤timefun_arg("chouchou")
# 2. 将步骤1得到的返回值,即time_fun返回, 然后time_fun(foo)
# 3. 将time_fun(foo)的结果返回,即wrapped_func
# 4. 让foo = wrapped_fun,即foo现在指向wrapped_func
@timefun_arg("chouchou")
def foo():
print("I am foo")
@timefun_arg("python")
def too():
print("I am too")
foo()
sleep(2)
foo()
too()
sleep(2)
too()
6 一个装饰器同时装饰多个函数.
装饰器在被装饰函数调用前就已经开始装饰, 装饰器每装饰一个函数, 就创建一个闭包
7 多个装饰器装饰同一个函数
# –*– coding: utf-8 –*–
# @Time : 2019/2/26 22:40
# @Author : Damon_duanlei
# @FileName : test05.py
# @BlogsAddr : https://blog.youkuaiyun.com/Damon_duanlei
def add_first(func):
print("开始装饰first功能")
def inner():
print("这是first的功能")
func()
return inner
def add_second(func):
print("开始装饰second功能")
def inner():
print("这是second的功能")
func()
return inner
@add_second
@add_first
def test():
print("------test------")
if __name__ == '__main__':
test()
运行结果:
>>>
开始装饰first功能
开始装饰second功能
这是second的功能
这是first的功能
------test------
结论: 如果多的装饰器装饰同一个函数, 离被装饰函数近的装饰器先开始装饰, 但执行顺序与装饰顺序相反.