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函数的属性值。

posted on 2017-12-21 20:59  迪米特  阅读(146)  评论(0编辑  收藏  举报

导航