情景:
当我们调用函数的时候,函数调用完成之后,函数内定义的变量都会被销毁,但是我们有时候需要保存函数内的这个变量,每次在这个变量的基础上完成一系列的操作,比如:每次在这个变量的基础上和其他数字进行求和计算。
我们可以通过闭包来实现这个问题。
闭包作用:
闭包可以保存函数内的变量,不会随着函数调用完成而销毁。
闭包的定义
在函数嵌套调用的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,我们把这个使用外部函数的内部函数称为闭包。
闭包构成条件:
1、在函数嵌套的前提下
2、内部函数使用了外部函数的变量(还包括外部函数的参数)
3、外部函数返回了内部函数
结论:闭包可以对外部函数的变量进行保存
闭包内修改外部变量
def func_out(num1):
def func_inner(num2):
num1 = num2 + 10
return func_inner
问题:在func内中的num1,能否通过func_inner内的 num1=num2+10 进行修改
通过下面代码演示可以看出,inner内部的num1 相当于在inner作用域内重新定义了一个变量num1,是不会影响外部函数out的num1的。
使用 nonlocal关键字,可以在内部函数使用外部函数的参数。
和global关键一样,在函数内部使用全局变量。
使用nonlocal关键字之后
装饰器的作用
在不改变原有函数的源代码的情况下,给函数增加新的功能
装饰器的功能特点:
1、不修改已有函数的源代码
2、给已有函数增加额外的功能
装饰器使用步骤
1、定义一个装饰器(装饰器的本质是闭包)
2、使用装饰器装饰函数
自己总结:因为闭包,在内部函数里面使用了外部函数的参数,所以在外部函数使用结束之后,因为内部函数引用的关系,外部函数的参数并不会消失。
现在将需要被装饰的函数(源函数),作为外部函数的参数传入,在内部函数里面对 源函数 进行代码增强并且调用源函数。
这样子,就达到了不修改 原函数 但是起到了增加额外功能的目的。
语法糖简化代码
# comment = check(comment)
# 解释器遇到@check 会立即执行comment = check(comment)
@check
def comment():
print("发表评论")
装饰器使用实例
获得函数执行时间
这个装饰器可以在任意函数进行使用,代码复用性大大提高
import time
# 定义装饰器
def get_time(fn):
def inner():
start_time = time.time()
fn()
enn_time = time.time()
print("时间:", enn_time - start_time)
return inner
# 被装饰的函数
@get_time
def func():
for i in range(100000):
print(i)
func()
装饰带有参数的函数
最后结论就是:inner参数要和被装饰的函数保质一致,因为最终返回的是inner
装饰带有返回值的函数
只要你明白上面的了,就可以举一反三明白下面的, inner 对标 被装饰后的,那么你装饰之前有什么,就在inner里面补什么。
装饰带有不定长参数的函数
和第一个装饰带有参数的是同样的道理
通用装饰器
def logging(fn): # fn = sum_num
def inner(*args, **kwargs):
print("装饰代码")
return fn(*args, **kwargs)
return inner
@logging # sum_num = logging(sum_num) ; 所以现在sum_num = inner
def sum_num(*args, **kwargs):
print(args)
print(kwargs)
return None
print("装饰之后:", sum_num(1, 2, 3, 4, 5, 6, age=19))
多个装饰器的使用
# 装饰器1
def check1(fn):
def inner():
print("登录验证1")
fn()
return inner
# 装饰器2
def check2(fn):
def inner():
print("登录验证2")
fn()
return inner
# 需要被装饰的函数
# 装饰顺序和语法糖位置写的位置一致
@check2
@check1
def comment():
print("发表评论")
comment()
带有参数的装饰器
语法规则:装饰器的外部函数只能有一个参数,闭包不存在这个规则,只针对装饰器
但是哈,只是针对语法糖的规则
你不是用语法糖,上面语法规则不成立
如果你要使用语法糖进行传参数,那就只能多包一层。我觉得这个很鸡肋,还不如不使用语法糖
第一个@logging 第一层根本没有起到装饰器作用,外层的只是起到一个传参作用。因为add函数没有作为它的参数,虽然logging任然是闭包,但是针对add函数来说,logging作用只是传参,针对 flag函数来说,logging函数是闭包。(完美理解)
add = logging(“+”) = decorator(add) = inner(num1,num2)
类装饰器
__call__的使用
一旦一个类里面实现了 call方法,那么这个类创建的对象就是一个可调用对象,可以像调用函数一样进行调用。
类装饰器的实现,这个使用比较少,我感觉不好,和java相比,你这个类型都变了
同样语法糖规则也适用,comment已经变成了一个类