Python装饰器原理与实战:从基础验证到functools.wraps详解
本文深入解析Python装饰器的核心机制与实战应用。通过业务场景案例,阐述如何利用装饰器在不修改原有代码的前提下添加验证逻辑,遵循开发封闭原则。详细讲解装饰器处理不同数量参数的技巧、多层装饰器的执行顺序,以及使用functools.wraps保留原函数元信息的重要性,帮助开发者掌握高级Python编程技巧。
Python装饰器基础与业务场景
本文通过具体实例详细解析Python装饰器的功能与使用方法,旨在为读者提供清晰的参考指南。
1、基础准备
#### 第一波 ####
def foo():
print 'foo'
foo #表示是函数
foo() #表示执行foo函数
#### 第二波 ####
def foo():
print 'foo'
foo = lambda x: x + 1
foo() # 执行下面的lambda表达式,而不再是原来的foo函数,因为函数 foo 被重新定义了
2、业务场景引入
假设一家初创公司拥有N个业务部门和1个基础平台部门。基础平台负责提供底层核心功能,例如数据库操作、Redis调用及监控API等。业务部门在开发时,只需直接调用基础平台提供的接口即可。具体架构如下:
############### 基础平台提供的功能如下 ###############
def f1():
print 'f1'
def f2():
print 'f2'
def f3():
print 'f3'
def f4():
print 'f4'
############### 业务部门A 调用基础平台提供的功能 ###############
f1()
f2()
f3()
f4()
############### 业务部门B 调用基础平台提供的功能 ###############
f1()
f2()
f3()
f4()
公司运营初期一切正常,但早期基础平台开发人员未考虑权限验证问题,导致所有功能对任何人都开放。现在需要对基础平台的所有功能进行重构,增加验证机制:在执行任何功能前,必须先通过验证。
项目负责人将任务交给Low B,他的方案如下:
Low B与每个业务部门沟通,要求各部门自行编写验证代码,在调用基础平台功能前手动执行验证。这样基础平台代码无需修改。
当天,Low B被解雇……
项目负责人将任务交给Low BB,他的方案如下:
仅对基础平台代码进行重构,确保N个业务部门无需任何修改。
############### 基础平台提供的功能如下 ###############
def f1():
# 验证1
# 验证2
# 验证3
print 'f1'
def f2():
# 验证1
# 验证2
# 验证3
print 'f2'
def f3():
# 验证1
# 验证2
# 验证3
print 'f3'
def f4():
# 验证1
# 验证2
# 验证3
print 'f4'
############### 业务部门不变 ###############
### 业务部门A 调用基础平台提供的功能###
f1()
f2()
f3()
f4()
### 业务部门B 调用基础平台提供的功能 ###
f1()
f2()
f3()
f4()
一周后,Low BB也被解雇……
项目负责人将任务交给Low BBB,他的方案如下:
仅对基础平台代码进行重构,其他业务部门无需任何修改。
############### 基础平台提供的功能如下 ###############
def check_login():
# 验证1
# 验证2
# 验证3
pass
def f1():
check_login()
print 'f1'
def f2():
check_login()
print 'f2'
def f3():
check_login()
print 'f3'
def f4():
check_login()
print 'f4'
开发封闭原则与装饰器实现
项目负责人查看了Low BBB的实现方案,露出一丝欣慰的笑容,语重心长地与Low BBB交谈:
项目负责人指出:
编写代码应遵循开发封闭原则。虽然该原则主要面向面向对象开发,但也适用于函数式编程。简单来说,它规定已实现的功能代码不允许被直接修改,但允许通过扩展来增加功能,即:
封闭:对现有功能代码的修改限制 开放:对扩展性的支持
若将开发封闭原则应用于上述需求,则不允许在函数 f1、f2、f3、f4 的内部直接修改代码。为此,项目负责人给Low BBB提供了一个实现方案:
def w1(func):
def inner():
# 验证1
# 验证2
# 验证3
return func()
return inner
@w1
def f1():
print 'f1'
@w1
def f2():
print 'f2'
@w1
def f3():
print 'f3'
@w1
def f4():
print 'f4'
通过上述代码,仅对基础平台代码进行少量修改,即可实现在调用函数 f1、f2、f3、f4 之前自动执行【验证】操作,且其他业务部门无需进行任何代码调整。
Low BBB谨慎地询问:这段代码的内部执行原理是什么?
装饰器内部执行原理详解
项目负责人正欲发怒,突然Low BBB的手机掉落在地,恰好屏幕亮起显示其女友照片。项目负责人见状转怒为喜,决定收下Low BBB这个朋友,并开始详细讲解:
以 f1 函数为例单独分析:
def w1(func):
def inner():
# 验证1
# 验证2
# 验证3
return func()
return inner
@w1
def f1():
print 'f1'
当代码编写完成后(注意:此时函数尚未执行、未被调用、未被运行),Python解释器会从上到下逐行解释代码,具体步骤如下:
def w1(func): ==>将w1函数加载到内存
@w1
从表面看,解释器似乎只执行了这两行代码,因为在函数未被调用之前,其内部代码块不会被执行。
从表面看解释器确实执行了这两行,但 @w1 这一行代码背后隐藏着重要机制,@函数名 是Python的一种语法糖。
如上例中 @w1 内部会执行以下操作:
- 执行w1函数,并将 @w1 下面的 函数 作为w1函数的参数,即:@w1 等价于 w1(f1)
因此,内部就会去执行:
def inner: #验证 return f1() # func是参数,此时 func 等于 f1 return inner # 返回的 inner,inner代表的是函数,非执行函数 - 这实际上是将原来的 f1 函数作为参数传递给另一个函数。
- 将执行完的 w1 函数返回值赋值给 @w1 下方函数的函数名。 w1函数的返回值是: def inner: #验证 return 原来f1() # 此处的 f1 表示原来的f1函数 然后,将此返回值重新赋值给 f1,即: 新f1 = def inner: #验证 return 原来f1() 因此,以后业务部门想要执行 f1 函数时,实际上执行的是 新f1 函数。在 新f1 函数内部,先执行验证逻辑,再执行原来的 f1 函数,最后将 原来f1 函数的返回值返回给业务调用者。 这样既执行了验证功能,又执行了原 f1 函数的内容,并将原 f1 函数的返回值返回给业务调用者。
Low BBB,你明白了吗?如果没明白,我晚上去你家帮你解决吧!!!
先理解上述流程,后续还将继续更新……
装饰器参数处理与多层装饰
3、常见问题解答
问题:如果被装饰的函数带有参数,该如何处理?
单个参数的情况:
def w1(func):
def inner(arg):
# 验证1
# 验证2
# 验证3
return func(arg)
return inner
@w1
def f1(arg):
print 'f1'
两个参数的情况:
def w1(func):
def inner(arg1,arg2):
# 验证1
# 验证2
# 验证3
return func(arg1,arg2)
return inner
@w1
def f1(arg1,arg2):
print 'f1'
三个参数的情况:
def w1(func):
def inner(arg1,arg2,arg3):
# 验证1
# 验证2
# 验证3
return func(arg1,arg2,arg3)
return inner
@w1
def f1(arg1,arg2,arg3):
print 'f1'
问题:是否可以编写一个能装饰具有n个参数的函数的通用装饰器?
def w1(func):
def inner(*args,**kwargs):
# 验证1
# 验证2
# 验证3
return func(*args,**kwargs)
return inner
@w1
def f1(arg1,arg2,arg3):
print 'f1'
问题:一个函数可以被多个装饰器同时装饰吗?
def w1(func):
def inner(*args,**kwargs):
# 验证1
# 验证2
# 验证3
return func(*args,**kwargs)
return inner
def w2(func):
def inner(*args,**kwargs):
# 验证1
# 验证2
# 验证3
return func(*args,**kwargs)
return inner
@w1
@w2
def f1(arg1,arg2,arg3):
print 'f1'
问题:还有更高级的装饰器用法吗?
#!/usr/bin/env python
#coding:utf-8
def Before(request,kargs):
print 'before'
def After(request,kargs):
print 'after'
def Filter(before_func,after_func):
def outer(main_func):
def wrapper(request,kargs):
before_result = before_func(request,kargs)
if(before_result != None):
return before_result;
main_result = main_func(request,kargs)
if(main_result != None):
return main_result;
after_result = after_func(request,kargs)
if(after_result != None):
return after_result;
return wrapper
return outer
@Filter(Before, After)
def Index(request,kargs):
print 'index'
使用functools.wraps保留元信息
4、functools.wraps的使用
上述装饰器虽然完成了其应有的
功能,即:装饰器内的函数代指了原函数,但需注意这只是代指而非完全相等,原函数的元信息(如文档字符串)并未被赋值到装饰器函数内部。例如:函数的注释信息
未保留元信息的情况:
def outer(func):
def inner(*args, **kwargs):
print(inner.__doc__) # None
return func()
return inner
@outer
def function():
"""
asdfasd
:return:
"""
print('func')
如果使用 @functools.wraps 装饰装饰器内部的函数,那么就会同时保留元信息和函数属性。
保留元信息的情况:
def outer(func):
@functools.wraps(func)
def inner(*args, **kwargs):
print(inner.__doc__) # None
return func()
return inner
@outer
def function():
"""
asdfasd
:return:
"""
print('func')
更多关于Python相关内容感兴趣的读者可查看本站专题:《Python面向对象程序设计入门与进阶教程》、《Python数据结构与算法教程》、《Python函数使用技巧总结》、《Python字符串操作技巧汇总》、《Python编码操作技巧总结》及《Python入门与进阶经典教程》
希望本文所述对大家Python程序设计有所帮助。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













