美文网首页
Python闭包函数和函数装饰器笔记

Python闭包函数和函数装饰器笔记

作者: 枯藤疯鸦 | 来源:发表于2019-11-03 17:54 被阅读0次

    小白笔记
    仅记录常规操作中较为不熟悉的操作类型

    1、作用域
    作用域是程序运行时变量可被访问的范围,定义在函数内的变量是局部变量,局部变量的作用范围只能是函数内部范围内,它不能在函数外引用。
    定义在模块最外层的变量是全局变量,它是全局范围内可见的,当然在函数里面也可以读取到全局变量的。例如:

    num=10  # 全局作用域变量
    
    def foo():
        print(num)  # 10
    

    而在函数外部则不可以访问局部变量。例如:

    def foo():
        num=10
    
    print(num)  # NameError: name 'num' is not defined
    

    2、嵌套函数
    函数不仅可以定义在模块的最外层,还可以定义在另外一个函数的内部,像这种定义在函数里面的函数称之为嵌套函数(nested function)例如:

    def print_msg():
        # print_msg 是外围函数
        msg="zen of python"
        def printer():
            # printer是嵌套函数
            print(msg)
        printer()
    
    # 输出 zen of python
    print_msg()
    

    对于嵌套函数,它可以访问到其外层作用域中声明的非局部(non-local)变量,比如代码示例中的变量 msg 可以被嵌套函数 printer 正常访问。
    那么有没有一种可能即使脱离了函数本身的作用范围,局部变量还可以被访问得到呢?答案是闭包

    3、什么是闭包
    函数身为第一类对象,它可以作为函数的返回值返回,现在我们来考虑如下的例子:

    def print_msg():
        # print_msg 是外围函数
        msg="zen of python"
        def printer():
            # printer 是嵌套函数
            print(msg)
            return printer
    
    another=print_msg()
    
    # 输出 zen of python
    another()
    

    这段代码和前面例子的效果完全一样,同样输出 "zen of python"。不同的地方在于内部函数 printer 直接作为返回值返回了。

    一般情况下,函数中的局部变量仅在函数的执行期间可用,一旦 print_msg() 执行过后,我们会认为 msg变量将不再可用。然而,在这里我们发现 print_msg 执行完之后,在调用 another 的时候 msg 变量的值正常输出了,这就是闭包的作用。闭包使得局部变量在函数外被访问成为可能

    这里的 another 就是一个闭包,闭包本质上是一个函数,它有两部分组成,printer 函数和变量 msg。闭包使得这些变量的值始终保存在内存中。
    函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数,如上例中的 another()。

    闭包,顾名思义,就是一个封闭的包裹,里面包裹着自由变量,就像在类里面定义的属性值一样,自由变量的可见范围随同包裹,哪里可以访问到这个包裹,哪里就可以访问到这个自由变量。

    4、带参数的闭包

    def adder(x):
        def wrapper(y):
            return x + y
        return wrapper
    
    adder5 = adder(5)    # 执行完毕后,变量adder5指向了函数wrapper
    # 输出 15
    adder5(10)  # 执行是调用函数wrapper,传入参数 10,输出: 5(adder(5)中参数)+10 
    # 输出 11
    adder5(6)    # 5 + 6
    

    闭包,可以理解为嵌套函数的特殊形式,函数返回值为子函数,且子函数调用的上级函数变量,不被释放,可在函数外被多次调用.
    返回闭包时牢记一点:返回函数不要引用任何循环变量,否则后续会发生变化的变量

    def count():
        fs= []
        for i in range(1,4):
            def f():
                return i*i
            fs.append(f)
    
        '''
        返回函数引用变量i,但它并非立刻执行。
        等到3个函数都返回时,它们所引用的变量i已经变成了3
        因此最终结果全部为9
        '''
    
        return fs
    
    f1, f2, f3= count()
    print(f1(),f2(),f3())
    

    如果一定要引用循环变量怎么办?方法是再创建一个函数 g(j) ,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:

    def count():
        def g(j):
            def f():
                return j*j
            return f
        fs= []
        for i in range(1,4):
            fs.append(g(i))     # f(i)立刻被执行,因此i的当前值被传入f()
        return fs
    
    f1, f2, f3= count()
    print(f1(),f2(),f3())
    

    5、装饰器的概念
    对函数(或方法)进行装饰,可以在函数运行前,增强函数的功能
    严格来讲,装饰器就是一个闭包函数,它的参数是:要增强功能的函数

    import time
    
    def timer(func):
        def inner():
            start_time= time.time()
            func()    # 被增强的函数在这里执行
            end_time= time.time()
            print('程序运行时间:', end_time- start_time)
        return inner
    
    @timer
    def test():
        time.sleep(3)
        print('test!')
    
    if __name__== '__main__':
        test()
    

    对函数使用装饰器后,主函数中 test() 实际执行的是:timer(test)

    6、装饰器的运行顺序
    装饰器在被装饰的函数定义之后立即运行,通常是在导入时(即python加载模块时)

    def outer(func):
        print("%s outer running" % func)
        def inner():
            print("%s inner running" % func)
            return func()  # 如果return func 则不会运行func函数
        return inner
    
    @outer
    def test1():
        print("test1 runing")
    
    @outer
    def test2():
        print("test2 runing")
    
    def test3():
        print("test3 runing")
    
    def main():
        print("main runing")
        test1()
        test2()
        test3()
    
    if __name__ == "__main__":
        main()
    

    运行结果如下:

    # 装饰器运行结果
    <function test1 at 0x1040cc730> outer running    # 返回了函数 inner()
    <function test2 at 0x1040cc840> outer running
    
    # 主函数运行结果
    main runing
    <function test1 at 0x1040cc730> inner running    # 运行test1() 时,继续执行装饰器返回的 inner() 函数
    test1 runing
    <function test2 at 0x1040cc840> inner running
    test2 runing
    test3 runing
    

    可以看出,在函数test1、test2定义后,装饰器outer立即被执行。

    7、带参数的装饰器

    如果函数要增强的功能,需要按不同的定位(参数),输出不同的增强信息,就需要建立一个带参数的装饰器
    要实现带参数的装饰器,就要用两层的嵌套函数来实现

    def say_hello(country):
        def wrapper(func):
            def deco(*args,**kwargs):        # 最终的返回函数
                if country== "china":
                    print("你好!")
                elif country== "america":
                    print('hello.')
                else:
                    return
                # 真正执行函数的地方
                func(*args,**kwargs)
            return deco
        return wrapper
    
    @say_hello("china")
    def chinese():
        print("我来自中国。")
    
    @say_hello("america")
    def american():
        print("I am from America.")
    
    if __name__== '__main__':
        chinese()
        american()
    

    运行结果:

    你好!
    我来自中国。
    
    hello.
    I am from America.
    

    注:修饰器修饰过的函数,会把被修饰函数的‘ __ name __ ’变为返回函数‘inner’ 或 ‘deco’ ,为防止有些依赖函数签名的代码执行不出错,需要将原始函数的属性复制到返回函数中。具体实现在定义的修饰器的返回函数前,增加一个 @functools.wraps(func) 内部修饰器,如下:

    def outer(func):
        print("%s outer running" % func)
    
        @functools.wraps(func)      # 函数前需要导入 import functools 库  
        def inner():
            print("%s inner running" % func)
            return func()  # 如果return func 则不会运行func函数
        return inner
    

    同理,带参数的装饰器进行同样的处理即可

    参考链接:
    https://zhuanlan.zhihu.com/p/26934085
    https://zhuanlan.zhihu.com/p/68810314
    https://zhuanlan.zhihu.com/p/65968462
    https://www.liaoxuefeng.com/wiki/1016959663602400/1017451662295584

    相关文章

      网友评论

          本文标题:Python闭包函数和函数装饰器笔记

          本文链接:https://www.haomeiwen.com/subject/gpklbctx.html