什么是闭包呢?其实闭包不难,装饰器也没那么难懂,虽然我们也可以不使用装饰器来实现功能, 可是学习了装饰器后, 可以帮助我们更好的面向对象学习。
为什么要学习闭包和装饰器
因为学了以后,可以帮助我们写出更加简洁和可可复用的代码, 同时这个在面试的时候也经常会被问到。
闭包
什么是闭包
在函数嵌套的前提下, 内层函数引用了外层函数的变量(包括参数), 外层函数又把内层函数当作返回值进行返回;
这个内层函数+所引用的外层变量, 称之为”闭包”
闭包的简单示范
def test1():
num = 1
def test2():
print(num)
return test2
result = test1()
result()
这里就是把test1 内部的test2 函数作为返回值给返回了
闭包的注意事项(一)如果要对外层的变量进行修改, 就需要使用 nonlocal var 进行声明
这个时候,作用域的问题, 会认为是重新在内层函数中定义的变量, 而不是改的外层变量,下面是正确示范
def test1():
a = 10
def test2():
nonlocal a
a = 11
print("修改外层变量")
print(a)
# 未修改之前
print("未修改之前" + str(a))
# 修改外层变量
test2()
# 修改之后
print("修改外层变量后" + str(a))
return test2
test1()
闭包的注意事项(二)当闭包内,引用了一个, 后期会发生变化的变量时,一定要注意
def test(a):
a = 1
def test2():
print(a)
a = 2
return test2
newFunc = test(5)
newFunc()
比如上面这里, 外层变量a 修改了数值, test2() 在 test()里面只是函数声明, 并没有调用,
所以此时的 print(a) 中的变量a 只是一个变量标识符, 在 newFunc() 调用的时候才会去找 变量a 此时存储的数值, 所以传递出去的就是最近改变的值
闭包的深入用法
def test():
funcs = []
for i in range(1, 4):
def test2(num):
num = 1 + i
def inner():
print(num)
return inner
funcs.append(test2(i))
return funcs
newFuncs = test()
print(newFuncs)
newFuncs[0]()
newFuncs[1]()
newFuncs[2]()
装饰器
装饰器的作用:
在函数名以及函数体不改变的前提下, 给一个函数附加一些额外代码, 就是给某个函数, 增加一个提前操作.
简单例子:
def fss():
print("发说说")
def ftp():
print("发图片")
此时, 想要在现有的功能上,新增一个登陆验证的功能, 要怎么做呢?
方法1. 在业务逻辑代码里修改? 增加一个登陆验证操作.
这样子做的坏处是: 因为业务逻辑代码非常多, 就造成了, 每一份,逻辑代码, 在调用具体的功能函数之前都需要做一个验证操作, 代码冗余度比较大, 复用性也会变差, 代码的可维护性比较差.
方法2:直接在功能函数里面去修改, 方便代码的重用
这样子虽然是解决了一些冗余的问题, 但是依然有不足, 就是违背了单一职责的思想, 所谓的单一原则就是每个方法只做单一的功能, 而不是把其他的功能也做了.
同时 也是违背了”开放封闭”思想, 这是面向对象的重要思想, 有两点:1.已经写好的代码,尽可能不要修改 2. 如果想要新增功能, 可以在原有代码基础上,单独进行扩展
引出装饰器
这就是装饰器的最原始使用, 没有使用语法糖的情况下. check_login 就是 fss的装饰器, 这就是装饰器的设计原理,借助于闭包,在不修改原来的函数功能的情况下, 给它增加了一些功能.
需要自己手动 ck =check_login(fss) 这样来使用装饰器.
装饰器的语法糖
所谓的语法糖就是快捷实现方式
def check_login(func):
def inner():
print("测试登陆")
func()
return inner
@check_login
def say_word():
print("发说说")
我们直接用 @func 就可以实现装饰器的调用
这里可以看到装饰器是没有带参数的, 直接 @func
带参数的装饰器
如何给装饰器添加参数?就是在装饰器的外层再做一个闭包,因为不能直接在装饰器里有多个参数;
def get_zsq(char):
def zsq(func):
def inner():
print(char * 10)
func()
return inner
return zsq
@get_zsq("$") #语法糖, 相当于 fn_num = zsq(fn_num), 而且装饰器语法糖不支持多个传参, 会报错
def fn_num():
print("123")
fn_num()
另一个案例
def where_are_you_from(country):
def where_city(func):
def inner(city, number):
print("i am from " + country + "and my home in" + city + "and " + str(number) + " years")
# 这一步不要少了,不然被装饰的函数就不会执行了, 而且参数一致
func(city, number)
return inner
return where_city
@where_are_you_from("china")
def get_views(city, number):
print("are you ready?")
get_views("shenzhen", '3')
装饰器的叠加和执行顺序
从上到下装饰, 从下到上执行
被装饰的函数带参数,而且参数个数不固定怎么办
装饰器一般是用来装饰多个函数的, 如果不同的函数 , 它们的参数长度本身就不一定, 怎么办?
可以用不定参数解决, *args 就是把传递进来的实参打包成一个元祖来使用,**kwargs 打包关键字参数成dict给函数体调用.
在接收到参数后, 在闭包里, 要怎么拆包呢?元祖拆包 *args,而字典是直接 kwargs 就可以使用了
def zsq(func):
def inner(*args, **kwargs):
print("_" * 30)
print(args, kwargs)
func(*args, **kwargs)
return inner
因为在装饰的时候, 只是把被装饰函数的参数类型改成了 不定参数, 在装饰器内部, 可以获取到所有的参数, 可以根据业务,是否弄成全局变量进行处理,
还是回归到装饰器的内在,给被装饰的函数进行某些前置操作。
# 复习 *args
@zsq
def pnum(num, num2, num3):
print(num, num2, num3)
# 复习 **kwargs
@zsq
def go_home(*args, **kwargs):
print("调用完了")
pnum(15, 18, 20)
go_home("深圳", "北京", country="china", provins='广东')
因为在装饰的时候, 只是把被装饰函数的参数类型改成了不定参数,在装饰器内部,可以获取到所有的参数,可以根据业务,是否弄成全局变量进行处理,还是回归到装饰器的内在,给被装饰的函数进行某些前置操作
被装饰的函数有返回值怎么办?
对有返回值的函数进行装饰: 无论什么场景, 保证函数返回值一致,就是在装饰器里, 也要有返回值;
装饰器的通用写法, 假如被装饰的函数没有返回值, 那么也不受任何影响, 没有返回值就是None, 有返回值就返回.
def judge_login(func):
def inner(*args):
result = func(*args)
# 返回
return result
return inner
@judge_login
def fss(word):
print(word)
# 返回None
return
@judge_login
def fsp(word, news):
print(word, news)
# 有返回值
return "nothing is"
print("====")
result1 = fss("你好")
print(result1)
result2 = fsp("哈哈","晚上")
print(result2)