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 innerinner = 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 innerinner = 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 innerdef myfunc(): return 1mynewfunc = 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@foodef myfunc(): return 1result = 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@loggerdef foo1(x,y=1): return x*y@loggerdef foo2(): return 2foo1(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@logdef add(x): return x+1result = 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 wrapsdef log(func): @wraps(func) def with_log(*args, **kwargs): print('called..%s' % func.__name__) return func(*args, **kwargs) return with_log@logdef add(x): return x+1result = add(1)print(add.__name__)print('result..%s' % result)
运行结果:
called..add add result..2 wraps对with_log重新装饰,属性值更新为func函数的属性值。