【第十篇】闭包和装饰器
程序员文章站
2022-04-15 15:15:32
一、闭包 1.1 闭包的定义 在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,我们把这个使用外部函数变量的内部函数称为闭包。 1.2 闭包的构成 构成条件: 在函数嵌套(函数里面再定义函数)的前提下 内部函数使用了外部函数的变量(还包括外部函数的参数) 外部函数返回了内 ......
一、闭包
1.1 闭包的定义
在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,我们把这个使用外部函数变量的内部函数称为闭包。
1.2 闭包的构成
构成条件:
- 在函数嵌套(函数里面再定义函数)的前提下
- 内部函数使用了外部函数的变量(还包括外部函数的参数)
- 外部函数返回了内部函数
1 # 定义一个外部函数 2 def func_out(num1): 3 # 定义一个内部函数 4 def func_inner(num2): 5 # 内部函数使用了外部函数的变量(num1) 6 result = num1 + num2 7 print("结果是:", result) 8 # 外部函数返回了内部函数,这里返回的内部函数就是闭包 9 return func_inner 10 11 12 # 创建闭包实例 13 f = func_out(1) 14 # 执行闭包 15 f(2) 16 f(3) 17 18 # 执行结果 19 # 结果是: 3 20 # 结果是: 4
ps:通过上面的输出结果可以看出闭包保存了外部函数内的变量num1,每次执行闭包都是在num1 = 1 基础上进行计算。
闭包的作用:闭包可以保存外部函数内的变量,不会随着外部函数调用完而销毁。
注意:由于闭包引用了外部函数的变量,则外部函数的变量没有及时释放,消耗内存。
1.3 __closure__
判断函数是否是闭包函数:输出的__closure__有cell元素 :是闭包函数
1 def func(): 2 name = 'eva' 3 4 def inner(): 5 print(name) 6 print(inner.__closure__) # (<cell at 0x0000028dd6fd0e58: str object at 0x0000028dd70d1f48>,) 7 return inner 8 9 10 f = func() 11 f() 12 13 14 15 16 name = 'egon' 17 18 19 def func2(): 20 def inner(): 21 print(name) 22 print(inner.__closure__) # 结果为none,不是闭包函数,闭包函数是对外包作用域的引用,而非全局作用域 23 return inner 24 25 26 f2 = func2() 27 f2()
1.4 修改闭包中使用的外包变量
错误示例:
1 # 定义一个外部函数 2 def func_out(num1): 3 4 # 定义一个内部函数 5 def func_inner(num2): 6 # 这里本意想要修改外部num1的值,实际上是在内部函数定义了一个局部变量num1 7 num1 = 10 8 # 内部函数使用了外部函数的变量(num1) 9 result = num1 + num2 10 print("结果是:", result) 11 12 print(num1) 13 func_inner(1) 14 print(num1) 15 16 # 外部函数返回了内部函数,这里返回的内部函数就是闭包 17 return func_inner 18 19 20 # 创建闭包实例 21 f = func_out(1) 22 # 执行闭包 23 f(2)
正确的示例:
1 # 定义一个外部函数 2 def func_out(num1): 3 4 # 定义一个内部函数 5 def func_inner(num2): 6 # 这里本意想要修改外部num1的值,实际上是在内部函数定义了一个局部变量num1 7 nonlocal num1 # 告诉解释器,此处使用的是 外部变量a 8 # 修改外部变量num1 9 num1 += 10 10 # 内部函数使用了外部函数的变量(num1) 11 result = num1 + num2 12 print("结果是:", result) 13 14 print(num1) 15 func_inner(1) 16 print(num1) 17 18 # 外部函数返回了内部函数,这里返回的内部函数就是闭包 19 return func_inner 20 21 22 # 创建闭包实例 23 f = func_out(1) 24 # 执行闭包 25 f(2)
1.5 闭包的嵌套
1 def wrapper(): 2 money = 1000 3 def func(): 4 name = 'eva' 5 def inner(): 6 print(name, money) 7 return inner 8 return func 9 10 11 f = wrapper() 12 i = f() 13 i()
二、装饰器
2.1 装饰器的定义
就是给已有函数增加额外功能的函数,它本质上就是一个闭包函数。
装饰器函数的特点:
- 不修改已有函数的源代码
- 不修改已有函数的调用方式
- 给已有函数增加额外的功能
2.2 装饰器的示例
1 # 登录验证 2 def check(fn): 3 def inner(): 4 print("登录。。。") 5 fn() 6 return inner 7 8 9 def comment(): 10 print("aa") 11 12 13 # 使用装饰器来装饰函数 14 comment = check(comment) 15 comment()
装饰器的基本雏形:
def wrapper(fn): # fn:目标函数. def inner(): '''执行函数之前''' fn() # 执行被装饰的函数 '''执行函数之后''' return inner
说明:
- 闭包函数有且只有一个参数,必须是函数类型,这样定义的函数才是装饰器
- 写代码要遵循开放封闭原则,它规定已经实现的功能代码不允许被修改,但可以被扩展
2.3 语法糖
python给提供了一个装饰函数更加简单的写法,那就是语法糖,语法糖的书写格式是: @装饰器名字,通过语法糖的方式也可以完成对已有函数的装饰
import time def timer(func): def inner(): start = time.time() func() print(time.time() - start) return inner @timer #==> func1 = timer(func1) def func1(): print('in func1') func1()
说明:
- @timer 等价于func1 = timer(func1)
- 装饰器的执行时间是加载模块时立即执行的
2.4 通用装饰器
2.4.1 装饰带有参数的函数
1 # 添加输出日志的功能 2 def logging(fn): 3 4 def inner(num1, num2): 5 print("--正在努力计算--") 6 fn(num1, num2) 7 8 return inner 9 10 11 # 使用装饰器装饰函数 12 @logging 13 def sum_num(a, b): 14 result = a + b 15 print(result) 16 17 18 sum_num(1, 2)
2.4.2 装饰不定长参数的函数
1 import time 2 3 4 # 计算执行时间 5 def timer(func): 6 def inner(*args, **kwargs): 7 start = time.time() 8 re = func(*args, **kwargs) 9 print(time.time() - start) 10 return re 11 return inner 12 13 14 # 使用语法糖装饰函数 15 @timer # ==> func1 = timer(func1) 16 def func1(a, b): 17 print('in func1', a, b) 18 19 20 # 使用语法糖装饰函数 21 @timer # ==> func2 = timer(func2) 22 def func2(a): 23 print('in func2 and get a:%s'%(a)) 24 return 'fun2 over' 25 26 27 func1('aaaaaa', 'bbbbbb') 28 print(func2('aaaaaa'))
2.4.3 装饰带有返回值的函数
1 import time 2 3 4 def timer(func): 5 def inner(*args, **kwargs): 6 start = time.time() 7 re = func(*args, **kwargs) 8 print(time.time() - start) 9 return re 10 return inner 11 12 13 @timer # ==> func2 = timer(func2) 14 def func2(a): 15 print('in func2 and get a:%s' % a) 16 return 'fun2 over' 17 18 19 func2('aaaaaa') 20 print(func2('aaaaaa'))
2.4.4 装饰器的完善
上面的装饰器已经非常完美了,但是正常我们情况下查看函数的一些信息的方法在此处都会失效
1 def index(): 2 '''这是一个主页信息''' 3 print('from index') 4 5 6 print(index.__doc__) # 查看函数注释的方法 7 print(index.__name__) # 查看函数名的方法
为了不让他们失效,我们还要在装饰器上加上一点来完善它:
1 from functools import wraps 2 3 4 def deco(func): 5 @wraps(func) # 加在最内层函数正上方 6 def wrapper(*args, **kwargs): 7 return func(*args, **kwargs) 8 return wrapper 9 10 11 @deco 12 def index(): 13 '''哈哈哈哈''' 14 print('from index') 15 16 17 print(index.__doc__) # 哈哈哈哈 18 print(index.__name__) # index
2.5 多个装饰器的使用
1 def make_div(func): 2 """对被装饰的函数的返回值 div标签""" 3 def inner(*args, **kwargs): 4 return "<div>" + func() + "</div>" 5 return inner 6 7 8 def make_p(func): 9 """对被装饰的函数的返回值 p标签""" 10 def inner(*args, **kwargs): 11 return "<p>" + func() + "</p>" 12 return inner 13 14 15 # 装饰过程: 1 content = make_p(content) 2 content = make_div(content) 16 # content = make_div(make_p(content)) 17 @make_div 18 @make_p 19 def content(): 20 return "人生苦短" 21 22 23 result = content() 24 25 print(result)
注:多个装饰器可以对函数进行多个功能的装饰,装饰顺序是由内到外的进行装饰
2.6 带有参数的装饰器
带有参数的装饰器就是使用装饰器装饰函数的时候可以传入指定参数,语法格式: @装饰器(参数,...)
1 # 添加输出日志的功能 2 def logging(flag): 3 4 def decorator(fn): 5 def inner(num1, num2): 6 if flag == "+": 7 print("--正在努力加法计算--") 8 elif flag == "-": 9 print("--正在努力减法计算--") 10 result = fn(num1, num2) 11 return result 12 return inner 13 14 # 返回装饰器 15 return decorator 16 17 18 # 使用装饰器装饰函数 19 @logging("+") 20 def add(a, b): 21 result = a + b 22 return result 23 24 25 @logging("-") 26 def sub(a, b): 27 result = a - b 28 return result 29 30 31 result = add(1, 2) 32 print(result) 33 34 result = sub(1, 2) 35 print(result)
ps:类装饰器在面向对象章节讲解
上一篇: Go基础编程实践(四)—— 数组和map