深拷贝含包装函数的对象后方法调用异常:原因与解决方案
问题分析与解答
1. 为什么代码不符合预期?
问题的核心在于绑定方法(bound method)的特性和copy.deepcopy的复制逻辑:
- 当
first_container初始化时,self.show = self._show_method触发了setter,把self._show设为do_wrapping(self._show_method)。这里的self._show_method是一个绑定方法——它已经和first_container实例绑定,内部的self指向的就是原实例。 - 装饰器
do_wrapping返回的wrapper函数,执行时会直接调用这个已经绑定好的func(也就是first_container._show_method)。 - 当你用
deepcopy复制实例时,second_container会原封不动复制first_container的_show属性,也就是那个已经绑定原实例的wrapper函数。所以调用second_container.show()时,实际执行的是first_container._show_method(),自然输出原实例的var=1;而second_container._show_method()是新实例的绑定方法,指向自身,所以输出0。
2. 正确的修复方式
核心思路是让装饰后的函数在调用时,使用当前调用它的实例作为self,而非提前绑定原实例。以下是几种可行方案:
方案一:修改装饰器,接收未绑定方法并动态绑定当前实例
把类的未绑定方法(而非实例的绑定方法)传给装饰器,让wrapper显式接收self参数,调用时动态绑定当前实例:
import copy from functools import wraps def do_wrapping(func): @wraps(func) def wrapper(self, *args): return func(self, *args) return wrapper class Container: def __init__(self, var): self.var = var # 传入类的未绑定方法,而非实例的绑定方法 self.show = Container._show_method @property def show(self): return self._show @show.setter def show(self, shower): self._show = do_wrapping(shower) def _show_method(self): print(self.var) first_container = Container(1) second_container = copy.deepcopy(first_container) second_container.var = 0 second_container.show() # 输出0 second_container._show_method() # 输出0
方案二:保存方法名,调用时动态获取当前实例的方法
如果包装逻辑复杂,可以在show属性中保存方法名称,调用时动态从当前实例获取对应方法并装饰:
import copy from functools import wraps def do_wrapping(func): @wraps(func) def wrapper(*args): return func(*args) return wrapper class Container: def __init__(self, var): self.var = var self.show = "_show_method" # 保存方法名 @property def show(self): # 调用时动态绑定当前实例的方法 func = getattr(self, self._show_name) return do_wrapping(func) @show.setter def show(self, shower_name): self._show_name = shower_name def _show_method(self): print(self.var) first_container = Container(1) second_container = copy.deepcopy(first_container) second_container.var = 0 second_container.show() # 输出0 second_container._show_method() # 输出0
方案三:自定义__deepcopy__方法,复制时重新绑定
如果不想改动原有属性逻辑,可以实现__deepcopy__方法,手动处理_show属性的复制,确保它绑定到新实例:
import copy from functools import wraps def do_wrapping(func): @wraps(func) def wrapper(*args): return func(*args) return wrapper class Container: def __init__(self, var): self.var = var self.show = self._show_method @property def show(self): return self._show @show.setter def show(self, shower): self._show = do_wrapping(shower) def _show_method(self): print(self.var) def __deepcopy__(self, memo): new_instance = Container(self.var) # 手动绑定新实例的_show_method new_instance.show = new_instance._show_method memo[id(self)] = new_instance return new_instance first_container = Container(1) second_container = copy.deepcopy(first_container) second_container.var = 0 second_container.show() # 输出0 second_container._show_method() # 输出0
你可以根据实际场景的复杂度选择最合适的方案,比如装饰器逻辑固定选方案一,需要动态切换方法选方案二,不想改动原有逻辑选方案三。
内容的提问来源于stack exchange,提问作者Zaccharie Ramzi
相关产品推荐
相关产品推荐

