Python:如何安装pass-through copy / deepcopy hook

时间:2015-01-20 17:48:27

标签: python

我有一个库,用于在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已定义__copy__,这很容易,因为我可以关闭copy_with_hook并调用它。但是,通常它没有定义。这里叫什么?这显然不能copy.copy,因为这会导致无限递归。

我发现this question似乎问了完全相同的问题,但是答案是错误的,因为这导致了深层复制,而不是副本。我也无法做到这一点,因为我需要为deepcopy和copy安装钩子。 (顺便说一下,我会在这个问题上继续讨论,但没有声誉,我无法做到这一点。)

我看了一下复制模块做了什么,这是一堆涉及__reduce_ex()的伏都教。我显然可以将其剪切/粘贴到我的代码中,或者直接调用它的私有方法,但我认为这是绝对的最后手段。这看起来很简单,我确信我错过了一个简单的解决方案。

2 个答案:

答案 0 :(得分:1)

基本上,您需要(A)复制并保留原始__copy__(如果存在)(并委托给它),否则(B)欺骗copy.copy进入使用您新添加的__copy__(并委托给copy,copy)。

所以,例如......:

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来玩,没有猴子修补,也没有复制粘贴{ - 1}}本身: - )

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

答案 1 :(得分:0)

经过一段时间的演奏后,我想出了以下内容:

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 的返回值的唯一细节是元组中的第一个元素是创建者函数。所有其他详细信息将移交给现有库代码。它并不完美,因为我仍然没有找到一种方法来检测目标类是否已经注册了一个选择器。