Python的装饰器使用详解
|
Python有大量强大又贴心的特性,如果要列个最受欢迎排行榜,那么装饰器绝对会在其中。 初识装饰器,会感觉到优雅且神奇,想亲手实现时却总有距离感,就像深闺的冰美人一般。这往往是因为理解装饰器时把其他的一些概念混杂在一起了。待我抚去层层面纱,你会看到纯粹的装饰器其实蛮简单直率的。 装饰器的原理 在解释器下跑个装饰器的例子,直观地感受一下。 >>> @make_bold ... def get_content(): ... return 'hello world' ... >>> get_content() '<b>hello world</b>' 被 make_bold 装饰的 get_content ,调用后返回结果会自动被 b 标签包住。怎么做到的呢,简单4步就能明白了。 1. 函数是对象 我们定义个 get_content 函数。这时 get_content 也是个对象,它能做所有对象的操作。 def get_content(): return 'hello world' 它有 id ,有 type ,有值。 >>> id(get_content) 140090200473112 >>> type(get_content) <class 'function'> >>> get_content <function get_content at 0x7f694aa2be18> 跟其他对象一样可以被赋值给其它变量。 >>> func_name = get_content >>> func_name() 'hello world' 它可以当参数传递,也可以当返回值 >>> def foo(bar): ... print(bar()) ... return bar ... >>> func = foo(get_content) hello world >>> func() 'hello world' 2. 自定义函数对象 我们可以用 class 来构造函数对象。有成员函数 __call__ 的就是函数对象了,函数对象被调用时正是调用的 __call__ 。
class FuncObj(object):
def __init__(self,name):
print('Initialize')
self.name= name
def __call__(self):
print('Hi',self.name)
我们来调用看看。可以看到, 函数对象的使用分两步:构造和调用 (同学们注意了,这是考点)。
>>> fo = FuncObj('python')
Initialize
>>> fo()
Hi python
3. @ 是个语法糖 装饰器的 @ 没有做什么特别的事,不用它也可以实现一样的功能,只不过需要更多的代码。 @make_bold def get_content(): return 'hello world' # 上面的代码等价于下面的 def get_content(): return 'hello world' get_content = make_bold(get_content) make_bold 是个函数,要求入参是函数对象,返回值是函数对象。 @ 的语法糖其实是省去了上面最后一行代码,使可读性更好。用了装饰器后,每次调用 get_content ,真正调用的是 make_bold 返回的函数对象。 4. 用类实现装饰器 入参是函数对象,返回是函数对象,如果第2步里的类的构造函数改成入参是个函数对象,不就正好符合要求吗?我们来试试实现 make_bold 。
class make_bold(object):
def __init__(self,func):
print('Initialize')
self.func = func
def __call__(self):
print('Call')
return '<b>{}</b>'.format(self.func())
大功告成,看看能不能用。 >>> @make_bold ... def get_content(): ... return 'hello world' ... Initialize >>> get_content() Call '<b>hello world</b>' 成功实现装饰器!是不是很简单? 这里分析一下之前强调的 构造 和 调用 两个过程。我们去掉 @ 语法糖好理解一些。 >>> get_content = make_bold(get_content) Initialize # 调用,实际上直接调用的是make_bold构造出来的函数对象 >>> get_content() Call '<b>hello world</b>' 到这里就彻底清楚了,完结撒花,可以关掉网页了~~~(如果只是想知道装饰器原理的话) 函数版装饰器 阅读源码时,经常见到用嵌套函数实现的装饰器,怎么理解?同样仅需4步。 1. def 的函数对象初始化 用 class 实现的函数对象很容易看到什么时候 构造 的,那 def 定义的函数对象什么时候 构造 的呢?
>>> globals()
{}
>>> def func():
... pass
...
>>> globals()
{'func': <function func at 0x10f5baf28>}
不像一些编译型语言,程序在启动时函数已经构造那好了。上面的例子可以看到,执行到 def 会才构造出一个函数对象,并赋值给变量 make_bold 。 这段代码和下面的代码效果是很像的。
class NoName(object):
def __call__(self):
pass
func = NoName()
2. 嵌套函数 Python的函数可以嵌套定义。
def outer():
print('Before def:',locals())
def inner():
pass
print('After def:',locals())
return inner
inner 是在 outer 内定义的,所以算 outer 的局部变量。执行到 def inner 时函数对象才创建,因此每次调用 outer 都会创建一个新的 inner 。下面可以看出,每次返回的 inner 是不同的。
>>> outer()
Before def: {}
After def: {'inner': <function outer.<locals>.inner at 0x7f0b18fa0048>}
<function outer.<locals>.inner at 0x7f0b18fa0048>
>>> outer()
Before def: {}
After def: {'inner': <function outer.<locals>.inner at 0x7f0b18fa00d0>}
<function outer.<locals>.inner at 0x7f0b18fa00d0>
3. 闭包 嵌套函数有什么特别之处?因为有闭包。
def outer():
msg = 'hello world'
def inner():
print(msg)
return inner
下面的试验表明, inner 可以访问到 outer 的局部变量 msg 。 >>> func = outer() >>> func() hello world 闭包有2个特点 这部分想深入可以去了解Python的LEGB规则。 4. 用函数实现装饰器 装饰器要求入参是函数对象,返回值是函数对象,嵌套函数完全能胜任。
def make_bold(func):
print('Initialize')
def wrapper():
print('Call')
return '<b>{}</b>'.format(func())
return wrapper
用法跟类实现的装饰器一样。可以去掉 @ 语法糖分析下 构造 和 调用 的时机。 >>> @make_bold ... def get_content(): ... return 'hello world' ... Initialize >>> get_content() Call '<b>hello world</b>' 因为返回的 wrapper 还在引用着,所以存在于 make_bold 命名空间的 func 不会消失。 make_bold 可以装饰多个函数, wrapper 不会调用混淆,因为每次调用 make_bold ,都会有创建新的命名空间和新的 wrapper 。 到此函数实现装饰器也理清楚了,完结撒花,可以关掉网页了~~~(后面是使用装饰的常见问题) 常见问题 1. 怎么实现带参数的装饰器? 带参数的装饰器,有时会异常的好用。我们看个例子。 >>> @make_header(2) ... def get_content(): ... return 'hello world' ... >>> get_content() '<h2>hello world</h2>' 怎么做到的呢?其实这跟装饰器语法没什么关系。去掉 @ 语法糖会变得很容易理解。 @make_header(2) def get_content(): return 'hello world' # 等价于 def get_content(): return 'hello world' unnamed_decorator = make_header(2) get_content = unnamed_decorator(get_content) 上面代码中的 unnamed_decorator 才是真正的装饰器, make_header 是个普通的函数,它的返回值是装饰器。 来看一下实现的代码。
def make_header(level):
print('Create decorator')
# 这部分跟通常的装饰器一样,只是wrapper通过闭包访问了变量level
def decorator(func):
print('Initialize')
def wrapper():
print('Call')
return '<h{0}>{1}</h{0}>'.format(level,func())
return wrapper
# make_header返回装饰器
return decorator
看了实现代码,装饰器的 构造 和 调用 的时序已经很清楚了。 >>> @make_header(2) ... def get_content(): ... return 'hello world' ... Create decorator Initialize >>> get_content() Call '<h2>hello world</h2>' 2. 如何装饰有参数的函数? (编辑:安卓应用网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
