Python装饰器原理与实战:从基础验证到functools.wraps详解

Leo
Leo
发布时间:2026-10-13| 2人浏览过
原创

本文深入解析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程序设计有所帮助。

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。