Python:如何安装直通复制/深度复制挂钩

Python:如何安装直通复制/深度复制挂钩,python,Python,我有一个库,在WeakKeyDictionary中存储外部用户对象的附加数据: extra_stuff = weakref.WeakKeyDictionary() def get_extra_stuff_for_obj(o): return extra_stuff[o] 复制用户对象时,我希望副本具有相同的额外内容。但是,我对用户对象的控制有限。我想为用户对象类定义一个类装饰器,它将以以下方式使用: def has_extra_stuff(klass): def copy_wi

我有一个库,在WeakKeyDictionary中存储外部用户对象的附加数据:

extra_stuff = weakref.WeakKeyDictionary()
def get_extra_stuff_for_obj(o):
    return extra_stuff[o]
复制用户对象时,我希望副本具有相同的额外内容。但是,我对用户对象的控制有限。我想为用户对象类定义一个类装饰器,它将以以下方式使用:

def has_extra_stuff(klass):
    def copy_with_hook(self):
        new = magic_goes_here(self)
        extra_stuff[new] = extra_stuff[self]
    klass.__copy__ = copy_with_hook
    return klass
如果klass已经定义了
\uuuuu copy\uuuuu
,那么这很容易,因为我可以用钩子在原始文件上关闭
copy\u并调用它。然而,它通常没有定义。这里叫什么?这显然不能是copy.copy,因为这将导致无限递归

我发现它似乎提出了完全相同的问题,但答案是错误的,因为这导致了一个深度复制,而不是复制。我也无法做到这一点,因为我需要为deepcopy和copy安装挂钩。(顺便说一句,我本来会继续讨论这个问题,但由于没有声誉,我不能这样做。)

我研究了复制模块的功能,这是一系列涉及
\uuuu reduce\uex()
的巫术。我可以明显地把它粘贴到我的代码中,或者直接调用它的私有方法,但我会认为这是绝对的最后手段。这看起来很简单,我确信我缺少了一个简单的解决方案。

本质上,你需要(a)复制并保留原始的
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu(并委托给
复制,复制

所以,例如…:

import copy
import threading

copylock = threading.RLock()

def has_extra_stuff(klass):

    def simple_copy_with_hook(self):
        with copylock:
            new = original_copy(self)
            extra_stuff[new] = extra_stuff[self]

    def tricky_case(self):
        with copylock:
            try:
                klass.__copy__ = None
                new = copy.copy(self)
            finally:
                klass.__copy__ = tricky_case
            extra_stuff[new] = extra_stuff[self]

    original_copy = getattr(klass, '__copy__', None)
    if original_copy is None:
        klass.__copy__ = tricky_case
    else:
        klass.__copy__ = simple_copy_with_hook
    return klass
这不是有史以来最优雅的代码,但至少它只是在玩
klass
,没有猴子补丁,也没有复制和粘贴
copy.py
本身:-)


补充:由于OP在评论中提到,他无法使用此解决方案,因为该应用程序是多线程的,因此添加了适当的锁定以使其实际可用。使用单个全局重入锁来确保避免由于多个线程之间无序获取多个锁而导致的死锁,并且可能“以防万一”过度锁定,尽管我怀疑简单情况和棘手情况下的dict赋值可能不需要锁。。。但是,当线程受到威胁时,安全总比抱歉好:-)

玩了一番之后,我想到了以下几点:

import copy_reg, copy

# Library
def hook(new):
    print "new object: %s" % new

def pickle_hooked(o):
    pickle = o.__reduce_ex__(2)
    creator = pickle[0]

    def creator_hook(*args, **kwargs):
        new = creator(*args, **kwargs)
        hook(new)
        return new

    return (creator_hook,) + pickle[1:]

def with_copy_hook(klass):
    copy_reg.pickle(klass, pickle_hooked)
    return klass

# Application
@with_copy_hook
class A(object):
    def __init__(self, value):
        self.value = value

这注册了一个直通复制钩子,该钩子还具有用于复制和深度复制的优点。reduce_ex返回值唯一需要关注的细节是元组中的第一个元素是creator函数。所有其他详细信息将传递给现有库代码。它并不完美,因为我仍然看不到检测目标类是否已经注册了pickler的方法。

对不起,我应该在问题中说:我也想到了这一点。不幸的是,这个应用程序是多线程的,所以我不能使用这样的解决方案。@MatthewBooth,如果能在你的问题中提到这样一个关键的约束,你会很高兴,你知道吗?!不管怎么说,没有什么是一个全球性的拷贝所不能解决的。搞乱全局状态是一个雷区。这个应用程序碰巧使用了线程,但即使没有,我也尽量避免弄乱全局状态。此外,每当我发现自己需要为这样一个看似琐碎的问题锁定时,我都会尝试先在其他地方寻找长时间和困难。除了注册表本身的任何序列化要求外,这里没有要求线程序列化的根本原因。上面包含一个竞赛:a:copy.copy(o)a:o.\uuuuu copy\uuuuu存在a:lock copy锁a:klass.\uuuu copy\uuuuu=None B:copy.copy(o)B:o.\uuuuu copy\uuuuu不存在、格式化、提交编辑限制、所有内容。。。同一对象的2个同时副本:仅为其中一个对象调用挂钩。问题是,通过禁用复制挂钩,也会禁用锁定。i、 e.您不能在此处实施锁定。我认为下面的解决方案是好的。