装饰器在python面试里是你们必须迈过去的一道坎儿,在面试中非常常见。而且,在实际项目中,python装饰器也是一个非常有意和有用的功能,可以在不改变函数代码和调用方式的情况下给函数添加新的功能,广泛应用于权限校验、性能统计、日志打印等应用场景。本文会深入浅出的帮助大家理解装饰器,击破面试难点。
01
装饰器的由来
试想这样一种场景,当需要对某一些函数做耗时统计时怎么办?是在每一个函数的开头和结束各打印一个时间戳么?要是有几百函数怎么办?要是要修改计算的精度怎么办?ctrl+c&ctrl+v到山无棱么?有没有什么办法可以不侵入函数体,同时又代价最低呢,这个时候就需要使用到我们本文所讲到的装饰器。
02
基本概念
在理解装饰器之前,我们首先需要了解几个概念。
函数
在python里,一切皆对象,函数也是一个对象。函数名其实就是指向一段内存空间的地址。因此函数可以作为参数,也可以作为返回值。
嵌套函数
在函数体内又重新定义一个新的函数,称为嵌套函数。外部的我们叫它外函数,内部的我们叫他内函数。如下图就是一个嵌套函数,在outer的函数体内又定义了内函数inner。需要注意点一点是,当内函数在自己作用域内无法找到局部变量时,会向上一层作用域查找。具体的作用域相关的知识点在这里不详细介绍。
def outer():
a = 1
def inner():
b = a + 1
print(b)
inner()outer()
#输出结果 2
闭包
闭包是Python编程一个非常重要的概念。如果一个外函数中定义了一个内函数,且内函数体引用了外函数变量,同时外函数通过return返回内函数的引用时,会把定义时涉及到的外部引用变量和内函数打包成一个整体(闭包)返回。如下例。
def outer(out_param):
a = out_param
def inner(inner_param):
b = inner_param
print(a+b)
return inner
func = outer(1) # 返回inner函数对象+局部变量1(闭包)
func(10) # 相当于inner(10)。输出11
03
装饰器
有了上面的基础,就比较好理解装饰器。装饰器本质上是一个嵌套函数,它接受被装饰的函数(func)作为参数,并返回一个包装过的函数。通过这种方式扩展功能,主要有2个特点:a、不修改被装饰函数的调用方式;b、不修改被装饰函数的源代码。
无参装饰器
如下例通过@符号,实现嵌套函数的调用,记录函数执行时间。
import time
def outer(func): # 将test的地址传递给func
def inner():
start_time = time.time()
func() # func入参数是test函数对象地址
end_time = time.time()
print("运行时间为%s"%(end_time - start_time))
return inner # 返回inner的地址
@outer # 这里实际执行的是test = outer(test)
def test():
time.sleep(10)
print("test for decorator")
test()
# 输出
# test for decorator
# 运行时间为4.00339603424
有参装饰器
通过*args和**kwargs,实现参数传递
import timedef outer(func): # 将test的地址传递给funcdef inner(*args, **kwargs): start_time = time.time() func(*args, **kwargs) # func入参数是test函数对象地址 end_time = time.time() print("运行时间为%s"%(end_time - start_time)) return inner # 返回inner的地址@outerdef test(sleep_time): time.sleep(sleep_time) print("test for decorator")test(2)
# 输出
# test for decorator
# 运行时间为2.00034189224
类装饰器
装饰器不仅可以是函数,还可以是类,相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器主要依靠类的__call__方法(Python中,只要在创建类型的时候定义了__call__()方法,这个类型就是可调用的),当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。
class Foo(object):def __init__(self, func): self._func = func def __call__(self): print('class decorator running') self._func() print('class decorator ending')@Foo # 实际调用方式为:bar = Foo(bar)
def bar():print('bar')bar()
# 输出# class decorator runing# bar# class decorator ending
04
思考题
如果有多个装饰器的情况下执行顺序是怎样的呢?
求职礼包
《2020届互联网名企最新校招信息汇总》《399套精选求职简历模板,只为免费送你》求职面经
《校招凉面-今日头条&京东算法岗面经》《校招热面-渣渣学长给学弟学妹的求职建议》《职场-互联网955上班公司名单-不要996》《爆尿-互联网公司福利大比拼》