Python 装饰器原理深度解析:从基础到带参实现
本文深入解析 Python 自定义装饰器的底层工作原理。通过对比基础装饰器与带 Wrapper 函数的装饰器,详细阐述 `@deco` 语法糖在函数定义时的执行逻辑,包括函数地址的传递、`myfunc` 指向 `wrapper` 的过程,以及为何只有带 Wrapper 的装饰器能正确传递参数并每次执行。
Python 装饰器基础示例
本文通过具体实例详细讲解 Python 中自定义装饰器的实现方式,旨在帮助读者深入理解其工作机制,供参考学习。
首先观察一个基础示例。
def deco(func):
print("before myfunc() called.")
func()
print("after myfunc() called.")
return func
@deco
def myfunc():
print("myfunc() called.")
# myfunc = deco(myfunc) # 与上面的@deco等价
myfunc()
print("***********")
myfunc()
运行该代码后,控制台输出如下内容:
before myfunc() called. myfunc() called. after myfunc() called. myfunc() called. *********** myfunc() called.
从输出结果可以看出,装饰器内部的逻辑仅执行了一次。这是为什么呢?
原因在于,在 `myfunc()` 函数定义前添加 `@deco`,其本质等价于在 `def myfunc()` 语句之后,先将下方代码块的首地址传递给参数 `func`,紧接着执行 `myfunc = deco(myfunc)`。执行该行代码时,`func` 指向了原本定义的 `myfunc()` 函数体,同时将 `myfunc` 函数的地址传递给 `deco()` 函数(即 `myfunc -> func`,此时两者值相同)。随后执行装饰器内部逻辑,最终返回 `func` 并重新赋值给 `myfunc`。此后调用 `myfunc()` 时,仅打印“myfunc() called”。第二次调用时,依然只打印该语句。之所以第二次未执行装饰器内部逻辑,是因为 `myfunc = deco(myfunc)` 仅执行了一次,而该语句才是真正触发装饰器内部代码的关键。
带 Wrapper 的装饰器实现
上述代码表明,基础装饰器仅对函数的首次定义过程进行了“装饰”。若希望每次调用函数时都执行装饰逻辑,该如何实现?继续往下看。
def deco(func):
def wrapper(*args, **kwargs): # *args, **kwargs用于接收func的参数
print("before myfunc() called.")
func(*args, **kwargs)
print("after myfunc() called.")
return wrapper
@deco
def myfunc(a, b):
print(a+b)
# myfunc = deco(myfunc) # 与上面的@deco等价
myfunc(1, 2)
print("***********")
myfunc(3, 4)
改进后的代码输出结果如下:
before myfunc() called. 3 after myfunc() called. *********** before myfunc() called. 7 after myfunc() called.
如前所述,在 `myfunc()` 定义前加 `@deco` 等价于先传递函数地址给 `func`,再执行 `myfunc = deco(myfunc)`。当执行 `myfunc(1, 2)` 时,`myfunc` 函数体的地址早已传递给 `deco()` 函数,此时 `deco` 返回的是 `wrapper`。因此,`myfunc` 所代表的地址不再是原 `myfunc` 的地址,而是 `wrapper` 函数的地址。此后凡调用 `myfunc()`,实际均在调用 `wrapper()`。即 `myfunc(1, 2)` 等同于 `wrapper(1, 2)`,从而确保每次调用 `myfunc()` 时,装饰器内部逻辑都会执行。而 `wrapper()` 内部的 `func` 则代表原始的 `myfunc()` 函数体。
函数地址传递机制解析
如何进一步理解“在出现 `def myfunc()` 后,先将下面所有内容的首地址传递给 `func`”这一过程?参考以下示例:
def deco(func):
def wrapper(*args, **kwargs): # *args, **kwargs用于接收func的参数
print("wrapper的地址:", wrapper)
func(*args, **kwargs)
print("func的地址:", func)
return wrapper
@deco
def myfunc(a, b):
print("myfunc的地址:",myfunc)
print(a+b)
# myfunc = deco(myfunc) # 与上面的@deco等价
myfunc(1, 2)
print("***********")
print("修改后myfunc的地址:",myfunc)
运行结果如下:
wrapper的地址: <function deco.<locals>.wrapper at 0x0000023AA9FF58C8> myfunc的地址: <function deco.<locals>.wrapper at 0x0000023AA9FF58C8> 3 func的地址: <function myfunc at 0x0000023AA9FF5840> *********** 修改后myfunc的地址: <function deco.<locals>.wrapper at 0x0000023AA9FF58C8>
当程序执行到 `myfunc(1,2)` 时,本质上是在执行 `wrapper(1, 2)`。此时先打印 `wrapper` 的地址,随后执行 `func()` 函数。在 `func()` 执行期间,打印 `myfunc()` 的地址(可见此时 `myfunc` 的值与 `wrapper` 相等),并打印结果 3。当输出 `func()` 函数的地址时,可见其与 `myfunc()` 函数的地址不同。这说明,原始定义的 `myfunc()` 函数体已归属 `func`,不再属于 `myfunc`。
装饰器执行流程验证
进一步验证这一机制:
def deco(func):
def wrapper(*args, **kwargs): # *args, **kwargs用于接收func的参数
pass
return wrapper
@deco
def myfunc(a, b):
print(a+b)
myfunc(1, 2)
该代码没有任何输出。原因是,执行 `myfunc(1, 2)` 时,本质执行 `wrapper(1, 2)`。而此处的 `wrapper(1, 2)` 未执行任何操作,故无输出。至于 `print(a+b)` 语句,其地址已归属 `func`,不会被直接调用。
关于带参数的装饰器,可参考其他相关文档。
如需了解更多 Python 相关知识,可查看本站专题:《Python数据结构与算法教程》、《Python Socket编程技巧总结》、《Python函数使用技巧总结》、《Python字符串操作技巧汇总》及《Python入门与进阶经典教程》。
希望本文所述内容能对大家的 Python 程序设计有所帮助。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













