首先让我们来了解一下装饰器的作用
软件开发中最重要的一条真理就是“不要重复自己的工作”,当我们已经写好了一个函数,并且也在其他地方调用了这个函数,那我们如何在不修改原函数的代码,也不修改其他调用这个函数的代码的条件下去拓展原函数的功能呢?
装饰器就可以解决这个问题,解决的方法也很简单,给原函数加上一个包装层,具体的思路就是,写一个装饰器函数,这个函数的作用就是包装原函数,并返回包装后的新函数。
总结来说装饰器就是一个函数,它可以接受一个函数作为输入并返回一个新的函数作为输出(这里是重点,记笔记)。
在学习装饰器之前,我们先了解两个概念
1.python的函数都是对象,让我们看两个例子
def hello():
print 'hello world'
hello() # hello world
# 函数也是一个对象,可以将它赋值给任何变量
# 这里需要注意的是,该语句并没有执行函数,只不过将函数赋值给了一个变量
study = hello
# 下面这句才是调用函数
study() # hello world
# 将hello删除,在调用study(),会是怎样的结果?
del hello # NameError: name 'hello' is not defined
study() # hello world
上面的例子,除了调用函数之外,其他的部分的操作和变量是一模一样的。
2.既然python中的函数是对象,那么以下对对象执行的操作,函数也可以完成:
- 赋值给变量
- 在对象中定义一个内部对象
让我们看例子
def hello():
def nei():
return 'hello wordl'
return nei
# 我们调用hello函数,看看返回的是什么
a = hello()
type(a) # function
# 可以看到,返回的是一个function对象
# 如果我要执行该函数呢?没错和上面那个例子一样,在参数后加上括号
a() # hello world
# 如果我们想直接返回该函数的调用结果又该怎样去修改上面的函数呢?
def hello():
def nei():
return 'hello wordl'
# 直接加上括号,表示调用该函数
return nei()
hello() # hello world
实现一个装饰器
直接看例子
# 原函数
def login():
print '执行原函数'
# 装饰器函数,传入原函数,返回包装后的新函数
def zsq(func):
def nei():
print '传入函数前的操作'
func() #执行原函数
print '传入函数后的操作'
return nei
# 将原函传入装饰器,覆盖原函数入口
>>> login = zsq(login)
# 调用被装饰后的函数
>>> login()
传入函数前的操作
执行原函数
传入函数后的操作
再让我们来看看python的装饰器是如何编写的,虽然代码不尽相同,不过实现的功能是一样的。
from functools import wraps
# 装饰器函数
def zsq(func):
@wraps(func) # 这个用于保存原函数的元数据,下一节会针对这个详细讲解
def nei():
print '传入函数前的操作'
func() #调用原函数
print '传入函数后的操作'
return nei
# 原函数
@zsq # 这句话等价于:login = zsq(login),覆盖了原函数的入口
def login():
print '原函数功能'
>>> login()
传入函数前的操作
原函数功能
传入函数后的操作
编写装饰器时保存函数的元数据
我们编写好了一个装饰器,但是当将它用在一个函数上时,一些重要的元数据,比如函数名、文档字符串、函数注解以及调用签名都丢失了。
那我们如何在编写装饰器时如何保存函数的元数据呢?,解决方案就是为包装函数添加functools库中的@wraps装饰器。
我们先来看看不添加@warps的效果
# 装饰器函数
def zsq(func):
def nei(*args, **kwargs):
print '传入函数前的操作'
func()
print '传入函数后的操作'
return nei
# 原函数
@zsq # 这句话等价于:login = zsq(login),覆盖了原函数的入口
def login():
'''
原函数注解
'''
print '原函数功能'
>>>login.__name__
'nei'
>>>login.__doc__
可以看到,原函数的注解和函数名都已经丢失,加上@wraps就可以防止这种情况的出现,保存原函数的元数据。
再让我们看看加了@wraps(func)的效果
# 装饰器函数
def zsq(func):
@wraps(func) # 保存原函数的元数据
def nei():
print '传入函数前的操作'
func()
print '传入函数后的操作'
return nei
# 原函数
@zsq # 这句话等价于:login = zsq(login),覆盖了原函数的入口
def login():
'''
原函数注解
'''
print '原函数功能'
>>> login.__name__
'login'
>>> print login.__doc__
原函数注解
参考文章
Python装饰器:简单装饰,带参数装饰与类装饰器
Python中如何在一个函数中加入多个装饰器?