博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
python装饰器
阅读量:6895 次
发布时间:2019-06-27

本文共 3249 字,大约阅读时间需要 10 分钟。

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

转载地址:http://dqzdl.baihongyu.com/

你可能感兴趣的文章
单片机编程入门学习 这几问你能回答吗?
查看>>
如何拆分PDF页面?这个简单方法你一定没用过
查看>>
邮件协议
查看>>
Responsive Web Design 响应式网页设计
查看>>
织梦cms列表页{dede:list}标签实现按文章权重weight排序的方法
查看>>
jira部署,主机迁移,数据库迁移,jira
查看>>
众美集团携手万科物业 树立济南人居高标准
查看>>
形象比你的头衔更重要 看过悦嘉丽形象锻造营你就知道为什么
查看>>
Oracle 数据库导出数据泵(EXPDP)文件存放的位置
查看>>
组播中如何把广播流量转为组播流量
查看>>
ZooKeeper服务命令
查看>>
利用虚拟化实现应用发布与网络隔离
查看>>
redis两种调用方式实例
查看>>
我的友情链接
查看>>
oracle分区表
查看>>
(转载)centos6.5下安装mysql
查看>>
宽带用户防范“***”***十大招式
查看>>
PHP5.4第一天—基本语法
查看>>
thinkphp 5.0 index.php被替换成首页内容,被注入恶意代码
查看>>
Linux定制自动安装
查看>>