Python装饰器原理与用法深入详解
本文深入解析Python装饰器的核心原理与多种用法。通过模拟面试场景,从基础计时功能入手,逐步演示如何处理带参函数、不定长参数以及多个装饰器的叠加效果。文章详细阐述了装饰器在无需修改原代码的前提下拓展函数功能的机制,并解析了装饰器执行顺序与装载过程,帮助开发者掌握这一Python高级特性。
Python装饰器概述与基础概念
本文详细介绍Python装饰器的核心原理及其实际应用方法,旨在帮助读者深入理解并掌握这一重要特性。
你是否熟悉Python? 当然熟悉。 那请解释一下Python装饰器。 装饰器?我平时很少用到。
上述对话源自一位朋友真实的面试经历。
----------------------------------------------分割线------------------------------------------------------------------------------
装饰器原理与基础实现
简而言之,Python装饰器是一种用于扩展原有函数功能的工具。其特殊性在于返回值仍为函数对象。使用装饰器的主要优势在于,无需修改原函数源代码即可为其添加新功能。 通常情况下,若需扩展函数代码,最直接的方式是侵入式修改,例如:
import time
def func():
print("hello")
time.sleep(1)
print("world")
这是一个基础函数示例。假设我们需要记录该函数的执行总耗时,最直观的做法如下:
#原始侵入,篡改原函数
import time
def func():
startTime = time.time()
print("hello")
time.sleep(1)
print("world")
endTime = time.time()
msecs = (endTime - startTime)*1000
print("time is %d ms" %msecs)
然而,若你的领导告知:“这段代码是公司核心资产,严禁直接修改。”此时该如何应对?我们可以模仿装饰器的逻辑自行实现:
#避免直接侵入原函数修改,但是生效需要再次执行函数
import time
def deco(func):
startTime = time.time()
func()
endTime = time.time()
msecs = (endTime - startTime)*1000
print("time is %d ms" %msecs)
def func():
print("hello")
time.sleep(1)
print("world")
if __name__ == '__main__':
f = func
deco(f)#只有把func()或者f()作为参数执行,新加入功能才会生效
print("f.__name__ is",f.__name__)#f的name就是func
此处定义了一个名为deco的函数,接收一个函数作为参数,并为其嵌入计时逻辑。此时你可以自信地向领导展示:无需改动原有代码,已成功拓展函数功能。
随后领导提出:“公司核心区域存在一千万个func()函数,从func01()到func1kw()。若按你的方案,需执行一千万次deco()函数,这将对服务器造成巨大负担。”
在准备离职之际,你偶然了解到“装饰器”这一概念,发现它恰好能解决上述痛点。
我们先构建一个最简化的装饰器,不使用任何语法糖或高级特性,以展示其原始形态:
#既不需要侵入,也不需要函数重复执行
import time
def deco(func):
def wrapper():
startTime = time.time()
func()
endTime = time.time()
msecs = (endTime - startTime)*1000
print("time is %d ms" %msecs)
return wrapper
@deco
def func():
print("hello")
time.sleep(1)
print("world")
if __name__ == '__main__':
f = func #这里f被赋值为func,执行f()就是执行func()
f()
这里的deco函数即为最原始的装饰器,它接收一个函数作为参数,并返回另一个函数。被接收的函数func()在返回的wrapper()内部执行。在func()前添加@deco后,func()便注入了计时功能。此后调用func()时,它已具备更多功能。
因此,装饰器如同一个注入符号:它既拓展了原函数功能,又无需侵入函数内部修改代码,也无需重复执行原函数。
#带有参数的装饰器
import time
def deco(func):
def wrapper(a,b):
startTime = time.time()
func(a,b)
endTime = time.time()
msecs = (endTime - startTime)*1000
print("time is %d ms" %msecs)
return wrapper
@deco
def func(a,b):
print("hello,here is a func for add :")
time.sleep(1)
print("result is %d" %(a+b))
if __name__ == '__main__':
f = func
f(3,4)
#func()
处理带参与不定长参数的装饰器
满足领导需求后,领导进一步提问:“你拓展的函数包含参数,甚至是不定长参数,你的装饰器能处理吗?”对此,你只需微笑应对。
#带有不定参数的装饰器
import time
def deco(func):
def wrapper(*args, **kwargs):
startTime = time.time()
func(*args, **kwargs)
endTime = time.time()
msecs = (endTime - startTime)*1000
print("time is %d ms" %msecs)
return wrapper
@deco
def func(a,b):
print("hello,here is a func for add :")
time.sleep(1)
print("result is %d" %(a+b))
@deco
def func2(a,b,c):
print("hello,here is a func for add :")
time.sleep(1)
print("result is %d" %(a+b+c))
if __name__ == '__main__':
f = func
func2(3,4,5)
f(3,4)
#func()
多个装饰器的叠加与执行顺序
最后,领导问道:“单个函数需添加多项功能,单个装饰器是否足够?装饰器能否支持多个叠加?”
随后你将以下代码展示给领导:
#多个装饰器
import time
def deco01(func):
def wrapper(*args, **kwargs):
print("this is deco01")
startTime = time.time()
func(*args, **kwargs)
endTime = time.time()
msecs = (endTime - startTime)*1000
print("time is %d ms" %msecs)
print("deco01 end here")
return wrapper
def deco02(func):
def wrapper(*args, **kwargs):
print("this is deco02")
func(*args, **kwargs)
print("deco02 end here")
return wrapper
@deco01
@deco02
def func(a,b):
print("hello,here is a func for add :")
time.sleep(1)
print("result is %d" %(a+b))
if __name__ == '__main__':
f = func
f(3,4)
#func()
'''
this is deco01
this is deco02
hello,here is a func for add :
result is 7
deco02 end here
time is 1003 ms
deco01 end here
'''
多个装饰器的执行顺序是从最后一个装饰器开始,依次执行至第一个装饰器,最后执行原函数本身。
引用评论区一位读者的示例:
def dec1(func):
print("1111")
def one():
print("2222")
func()
print("3333")
return one
def dec2(func):
print("aaaa")
def two():
print("bbbb")
func()
print("cccc")
return two
@dec1
@dec2
def test():
print("test test")
test()
输出结果如下:
aaaa 1111 2222 bbbb test test cccc 3333
装饰器装载过程与执行机制解析
装饰器外层函数与内层函数之间的代码不属于目标函数的一部分,而是在装载装饰器时执行的附加操作。
第17至20行展示了装饰器的装载过程,相当于执行了test=dect1(dect2(test))。此时先执行dect2(test),结果是输出aaaa,将func指向函数test,并返回函数two;接着执行dect1(two),结果是输出1111,将func指向函数two,并返回函数one,最后进行赋值。
此时用函数替代了原函数名test。第22行则是实际调用被装载的函数,此时实际执行的是函数one。运行到func()时执行函数two,再次运行到func()时执行未修饰的原始函数test。
更多关于Python相关内容感兴趣的读者可查看本站专题:《Python面向对象程序设计入门与进阶教程》、《Python数据结构与算法教程》、《Python函数使用技巧总结》、《Python字符串操作技巧汇总》、《Python编码操作技巧总结》及《Python入门与进阶经典教程》
希望本文所述内容对大家学习Python程序设计有所帮助。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









