来源:http://www.lightxue.com/under...python
Python有大量强大又贴心的特性,若是要列个最受欢迎排行榜,那么装饰器绝对会在其中。
刚接触装饰器,会以为代码很少却难以理解。其实装饰器的语法自己挺简单的,复杂是由于同时混杂了其它的概念。下面咱们一块儿抛去无关概念,简单地理解下Python的装饰器。闭包
在解释器下跑个装饰器的例子,直观地感觉一下app
# make_bold就是装饰器,实现方式这里略去 >>> @make_bold ... def get_content(): ... return 'hello world' ... >>> get_content() '<b>hello world</b>'
被make_bold装饰的get_content,调用后返回结果会自动被b标签包住。怎么作到的呢,简单4步就能明白了。函数
咱们定义个get_content函数。这时get_content也是个对象,它能作全部对象的操做。设计
它有id,有type,有值。code
>>> id(get_content) 140090200473112 >>> type(get_content) <class 'function'> >>> get_content <function get_content at 0x7f694aa2be18>
跟其余对象同样能够被赋值给其它变量。orm
>>> func_name = get_content >>> func_name() 'hello world'
它能够当参数传递,也能够当返回值对象
>>> def foo(bar): ... print(bar()) ... return bar ... >>> func = foo(get_content) hello world >>> func() 'hello world'
咱们能够用class来构造函数对象。有成员函数__call__的就是函数对象了,函数对象被调用时正是调用的__call__。ip
class FuncObj(object): def __init__(self, name): print('Initialize') self.name= name def __call__(self): print('Hi', self.name)
咱们来调用看看。能够看到,函数对象的使用分两步:构造和调用(同窗们注意了,这是考点)。get
>>> fo = FuncObj('python') Initialize >>> fo() "Hi python"
装饰器的@没有作什么特别的事,不用它也能够实现同样的功能,只不过须要更多的代码。
@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返回的函数对象。
入参是函数对象,返回是函数对象,若是第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>'
成功实现装饰器!是否是很简单?
这里分析一下以前强调的构造和调用两个过程。咱们去掉@语法糖好理解一些。
# 构造,使用装饰器时构造函数对象,调用了__init__ >>> get_content = make_bold(get_content) Initialize # 调用,实际上直接调用的是make_bold构造出来的函数对象 >>> get_content() Call '<b>hello world</b>'
到这里就完全清楚了,完结撒花,能够关掉网页了~~~(若是只是想知道装饰器原理的话)
阅读源码时,常常见到用嵌套函数实现的装饰器,怎么理解?一样仅需4步。
用class实现的函数对象很容易看到何时构造的,那def定义的函数对象何时构造的呢?
# 这里的全局变量删去了无关的内容 >>> globals() {} >>> def func(): ... pass ... >>> globals() {'func': <function func at 0x10f5baf28>}
不像一些编译型语言,程序在启动时函数已经构造那好了。上面的例子能够看到,执行到def会才构造出一个函数对象,并赋值给变量make_bold。
这段代码和下面的代码效果是很像的。
class NoName(object): def __call__(self): pass func = NoName()
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>
嵌套函数有什么特别之处?由于有闭包。
def outer(): msg = 'hello world' def inner(): print(msg) return inner
下面的试验代表,inner能够访问到outer的局部变量msg。
>>> func = outer() >>> func() hello world
闭包有2个特色
inner能访问outer及其祖先函数的命名空间内的变量(局部变量,函数参数)。
调用outer已经返回了,可是它的命名空间被返回的inner对象引用,因此还不会被回收。
这部分想深刻能够去了解Python的LEGB规则。
装饰器要求入参是函数对象,返回值是函数对象,嵌套函数彻底能胜任。
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。
到此函数实现装饰器也理清楚了,完结撒花,能够关掉网页了~~~(后面是使用装饰的常见问题)
带参数的装饰器,有时会异常的好用。咱们看个例子。
>>> @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>'
为了有条理地理解装饰器,以前例子里的被装饰函数有意设计成无参的。咱们来看个例子。
@make_bold def get_login_tip(name): return 'Welcome back, {}'.format(name)
最直接的想法是把get_login_tip的参数透传下去。
class make_bold(object): def __init__(self, func): self.func = func def __call__(self, name): return '<b>{}</b>'.format(self.func(name))
若是被装饰的函数参数是明确固定的,这么写是没有问题的。可是make_bold明显不是这种场景。它既须要装饰没有参数的get_content,又须要装饰有参数的get_login_tip。这时候就须要可变参数了。
class make_bold(object): def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): return '<b>{}</b>'.format(self.func(*args, **kwargs))
当装饰器不关心被装饰函数的参数,或是被装饰函数的参数多种多样的时候,可变参数很是合适。可变参数不属于装饰器的语法内容,这里就不深刻探讨了。
下面这么写合法吗?
@make_italic @make_bold def get_content(): return 'hello world'
合法。上面的的代码和下面等价,留意一下装饰的顺序。
def get_content(): return 'hello world' get_content = make_bold(get_content) # 先装饰离函数定义近的 get_content = make_italic(get_content)
Python的装饰器倍感贴心的地方是对调用方透明。调用方彻底不知道也不须要知道调用的函数被装饰了。这样咱们就能在调用方的代码彻底不改动的前提下,给函数patch功能。
为了对调用方透明,装饰器返回的对象要假装成被装饰的函数。假装得越像,对调用方来讲差别越小。有时光假装函数名和参数是不够的,由于Python的函数对象有一些元信息调用方可能读取了。为了连这些元信息也假装上,functools.wraps出场了。它能用于把被调用函数的__module__,__name__,__qualname__,__doc__,__annotations__赋值给装饰器返回的函数对象。
import functools def make_bold(func): @functools.wraps(func) def wrapper(*args, **kwargs): return '<b>{}</b>'.format(func(*args, **kwargs)) return wrapper
对比一下效果。
>>> @make_bold ... def get_content(): ... '''Return page content''' ... return 'hello world' >>> # 不用functools.wraps的结果 >>> get_content.__name__ 'wrapper' >>> get_content.__doc__ >>> # 用functools.wraps的结果 >>> get_content.__name__ 'get_content' >>> get_content.__doc__ 'Return page content'
实现装饰器时每每不知道调用方会怎么用,因此养成好习惯加上functools.wraps吧。