闭包和装饰器的学习理解


什么是闭包呢?其实闭包不难,装饰器也没那么难懂,虽然我们也可以不使用装饰器来实现功能, 可是学习了装饰器后, 可以帮助我们更好的面向对象学习。

为什么要学习闭包和装饰器

因为学了以后,可以帮助我们写出更加简洁和可可复用的代码, 同时这个在面试的时候也经常会被问到。

闭包

什么是闭包

在函数嵌套的前提下, 内层函数引用了外层函数的变量(包括参数), 外层函数又把内层函数当作返回值进行返回;
这个内层函数+所引用的外层变量, 称之为”闭包”
闭包的简单示范

def test1():
    num = 1
    def test2():
        print(num)
    
    return test2

result = test1()
result()

这里就是把test1 内部的test2 函数作为返回值给返回了

运行效果

有参数的举例

闭包的注意事项(一)如果要对外层的变量进行修改, 就需要使用 nonlocal var 进行声明

没有用 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='广东')


运行1
因为在装饰的时候, 只是把被装饰函数的参数类型改成了不定参数,在装饰器内部,可以获取到所有的参数,可以根据业务,是否弄成全局变量进行处理,还是回归到装饰器的内在,给被装饰的函数进行某些前置操作
运行2

被装饰的函数有返回值怎么办?

对有返回值的函数进行装饰: 无论什么场景, 保证函数返回值一致,就是在装饰器里, 也要有返回值;
装饰器的通用写法, 假如被装饰的函数没有返回值, 那么也不受任何影响, 没有返回值就是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)

跨模块引用装饰器

跨模块引用1
跨模块引用2
跨模块引用3


文章作者: 陌上人如玉
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 陌上人如玉 !
  目录