• python装饰器


    1.函数作用域
    1)访问变量时,会首先寻找本作用域是否存在该变量,若没有,则依次寻找外部作用域。

    val = 'test'
    def foo():
        print(id(val))
    foo()
    print(id(val))
    

    运行结果:38638064 38638064
    2)若在本作用域内,赋值一个变量,则该变量是一个全新的局部变量,与外部作用域变量毫无关系

    val = 'test'
    def foo():
        val = 'hello world'
        print(id(val))
    foo()
    print(id(val))
    

    运行结果: 38141744 38900208
    同理,以下示例会报错

    val = 'test'
    def foo():
        print(id(val))
        val = 'hello world'
        print(id(val))
    foo()
    print(id(val))
    

    运行结果:
    UnboundLocalError: local variable 'val' referenced before assignment
    因为,作用域内有赋值,则val在该作用域内是个全新的局部变量,和外部val没有丝毫关系,第一个print(id(val)),会先找到本作用域内定义的val,因此会报上述错误。

    2.函数的闭包

    def foo():
        val = 'test'
        print(id(val))
        def inner():
            print(id(val))
        return inner
    inner = foo()
    inner()
    

    运行结果:35428200 35428200
    根据上节说的函数作用域,输出结果应该是相同的,没有问题。
    问题的关键是,作用域是有范围的,当一个函数执行完毕后,作用域内的对象都释放了。
    为什么执行inner()还能访问到外部作用域内的val变量呢?
    这就是函数的闭包特性,嵌套函数能够记住它所处的封闭的命名空间(封闭命名空间内的对象不会释放)。
    如何查看内嵌函数的闭包空间呢?可以通过函数的__closure__属性。

    print(inner.__closure__)
    

    运行结果:
    7575400
    7575400
    (<cell at 0x00000000006B5618: str object at 0x0000000000739768>,)
    0x0000000000739768的十进制值是7575400

    嵌套函数使用外部函数参数:

    def foo(x):
        print(id(x))
        def inner():
            print(id(x))
        return inner
    inner = foo(1)
    inner()
    print(inner.__closure__)
    

    运行结果:
    1562953392
    1562953392
    (<cell at 0x0000000002135618: int object at 0x000000005D28C6B0>,)
    可见外部函数传入的参数(嵌套函数有使用的)也会存在于闭包空间。

    3.装饰器
    上例中,如果参数x也是一个函数,如下所示:

    def foo(x):
        def inner():
            return x() + 1
        return inner
    def myfunc():
        return 1
    mynewfunc = foo(myfunc)
    result = mynewfunc()
    print(result)
    

    运行结果:2
    内嵌函数对我们的主函数myfunc逻辑重新组织了一番,并且执行外部函数,返回这个新的内嵌函数mynewfunc,替换掉我们的主函数myfunc
    执行新的函数mynewfunc即执行内嵌函数,因为闭包空间的存在,调用myfunc函数,并加上1,所以执行结果为2

    外部函数foo,就被称作装饰器(重新装饰主函数myfunc,并返回装饰后的函数mynewfunc)
    作用是对主函数,进行修饰,减少了主函数代码的复杂性,如增加log等辅助操作。

    4.python装饰器符@
    在python中,使用装饰器符@,作用于被装饰主函数,特点是简洁明了。

    def foo(x):
        def inner():
            return x() + 1
        return inner
    
    @foo
    def myfunc():
        return 1
    
    result = myfunc()
    print(result)
    

    使用@直接将装饰器,放在主函数的上面,调用主函数时,会自动进行上节操作,返回一个拥有闭包空间的函数。

    5.*args,kwargs
    *args,
    kwargs表示参数列表,使用这两个参数,就可以解决参数可变的问题,让装饰器变得通用了

    def logger(func):
        def inner(*args, **kwargs):
            print('arguments:%s,%s'%(args,kwargs))
            return func(*args, **kwargs)
        return inner
    
    @logger
    def foo1(x,y=1):
        return x*y
    
    @logger
    def foo2():
        return 2
    
    foo1(5,4)
    foo2()
    

    运行结果:
    arguments:(5, 4),{}
    arguments:(),{}
    对于带参数的被装饰函数,装饰函数参数包含被装饰函数名,参数列表(python中可省略)
    不管是args类型的参数,还是kwargs类型的参数(存在于闭包空间),都可以顺利使用装饰器。

    6.functools.wraps装饰器
    当我们使用装饰器时,实际上是用装饰器重新包装了一个函数,包装后的函数,函数名等都会发生变化。
    如何保证函数名这些属性不变呢?使用functools.wraps装饰器,将原函数作为参数,更新装饰器的属性。

    def log(func):
        def with_log(*args, **kwargs):
            print('called..%s' % func.__name__)
            return func(*args, **kwargs)
        return with_log
    
    
    @log
    def add(x):
        return x+1
    
    result = add(1)
    print(add.__name__)
    print('result..%s' % result)
    

    运行结果:
    called..add
    with_log
    result..2
    可以看到,add.__name__函数名称变成了with_log,这是因为add=log(add),经过装饰器重新装饰以后,函数名称已经变成了with_log
    如何保持函数名这些属性不变呢?
    对with_log函数再次进行装饰,将装饰后的函数名称等修改为func对应的属性。

    from functools import wraps
    
    
    def log(func):
        @wraps(func)
        def with_log(*args, **kwargs):
            print('called..%s' % func.__name__)
            return func(*args, **kwargs)
        return with_log
    
    
    @log
    def add(x):
        return x+1
    
    result = add(1)
    print(add.__name__)
    print('result..%s' % result)
    

    运行结果:
    called..add
    add
    result..2
    wraps对with_log重新装饰,属性值更新为func函数的属性值。

  • 相关阅读:
    基于Spring aop写的一个简单的耗时监控
    Intellij Idea 15 旗舰版 破解
    设计模式之工厂模式
    IDE神器intellij idea的基本使用
    [js] js判断浏览器(转)
    java知识大全积累篇
    一些技术大牛的博客集锦(转)
    添加鼠标右击菜单
    java 方法调用绑定
    Android系列--DOM、SAX、Pull解析XML
  • 原文地址:https://www.cnblogs.com/shijingjing07/p/8082448.html
Copyright © 2020-2023  润新知