You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

深拷贝含包装函数的对象后方法调用异常:原因与解决方案

问题分析与解答

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 05:18:05