Python中函数赋值与装饰器的内部实现差异探究
好问题!其实这两种赋值场景的核心区别在于变量指向的对象类型以及引用的绑定方式,咱们一步步拆解:
一、直接赋值普通函数:简单的引用替换
你第一个例子里的func_1 = func_2,本质就是Python中变量的引用替换操作。
先看修正语法后的示例代码:
def func_1(x): print("func_1") print(x) def func_2(x): print("func_2") print(x) func_1 = func_2
在Python里,函数是一等对象——它们和字符串、列表一样,可以被赋值给变量。执行func_1 = func_2后,func_1这个变量就不再指向原来的func_1函数对象,而是切换指向func_2对应的函数对象。这时候调用func_1(123),本质就是调用func_2(123),自然会输出func_2和传入的参数。
这个过程没有创建新的函数对象,只是把变量的引用从一个函数切换到另一个而已。如果原来的func_1没有其他变量引用它,Python的垃圾回收机制就会把它清理掉。
二、装饰器模式的赋值:高阶函数+闭包的组合
你第二个例子里的foo = our_decorator(foo),看起来也是赋值,但本质是高阶函数返回新的闭包函数,再把变量指向这个新函数。
先看修正后的正确装饰器代码:
def our_decorator(func): def function_wrapper(x): print("Before calling " + func.__name__) func(x) # 这里调用的是传入的原func,不是全局变量foo print("After calling " + func.__name__) return function_wrapper def foo(x): print("Hi, foo has been called with " + str(x)) print("We call foo before decoration:") foo("Hi") print("We now decorate foo with f:") foo = our_decorator(foo) print("We call foo after decoration:") foo(42)
这里的our_decorator是一个高阶函数(接收函数作为参数,并且返回函数)。当你执行our_decorator(foo)时,它会把原foo函数对象传入,然后创建一个新的function_wrapper函数——这个函数是一个闭包,它会捕获外部作用域的func变量(也就是原foo函数)。
当你把foo赋值为这个function_wrapper后,调用foo(42)其实是调用这个闭包函数:它会先打印前置信息,然后调用捕获到的原foo函数,最后打印后置信息。这里的关键是,闭包里的func指向的是装饰时传入的原foo,而不是后来被替换的foo变量,所以不会出现递归。
三、为什么你的无参数装饰器会触发无限递归?
你写的错误版本装饰器代码(修正语法后)是这样的:
def our_decorator(): def function_wrapper(x): print("Before calling ") foo(x) # 这里引用的是全局变量foo print("After calling ") return function_wrapper def foo(x): print("Hi, foo has been called with " + str(x)) print("We call foo before decoration:") foo("Hi") print("We now decorate foo with f:") foo = our_decorator() print("We call foo after decoration:") foo(42)
问题出在function_wrapper里的foo(x):它没有捕获任何局部的函数引用,而是直接引用全局变量foo。当你执行foo = our_decorator()后,全局变量foo已经不再指向原来的foo函数了,而是指向了function_wrapper。
这时候调用foo(42),就是调用function_wrapper(42),里面的foo(x)又会调用function_wrapper(x),如此循环往复,就形成了无限递归,所以会一直打印"Before calling "。
四、两种赋值场景的核心差异对比
| 维度 | 直接赋值普通函数 | 装饰器模式赋值 |
|---|---|---|
| 引用目标 | 变量直接指向另一个已存在的函数对象 | 变量指向新创建的闭包函数,且该闭包持有原函数的引用 |
| 逻辑扩展性 | 仅替换函数引用,无额外逻辑扩展能力 | 可以在原函数执行前后添加自定义逻辑,保留原函数功能 |
| 变量绑定方式 | 即时的引用替换,原变量指向的对象会被回收(无其他引用时) | 闭包在装饰时捕获原函数的引用,后续原变量被替换不影响闭包内的调用 |
| 本质 | 简单的变量引用切换 | 高阶函数+闭包的组合,创建新的函数对象并替换引用 |
内容的提问来源于stack exchange,提问作者Kanishk Viman

