可以通过交换__dict__高效地交换两个类实例吗?

15 投票
4 回答
3013 浏览
提问于 2025-04-17 00:53

我有一个很大的类,里面有很多成员,而且有不少地方引用了这个类的实例。可惜的是(出于合理的原因),这些引用的方向都是错的。

我不想重新创建每个实例(这样还得到处找并更新所有引用的地方),也不想每次访问这个类时都增加一个额外的间接层,或者一个个地交换成员,所以我定义了一个方法:

def swap(self, other):
    assert(isinstance(other, self.__class__))
    self.__dict__, other.__dict__ = other.__dict__, self.__dict__

这样我就可以这样做:

instance_a.swap(instance_b)
# now all references to instance_a everywhere are as if instance_a is instance_b

问题是:这看起来运行得很好(我没有使用 __slots__),但我感觉可能有某种原因让我不应该这样做,是吗?


这是我实际的使用场景:

我有一个类型,它实现了比较操作符,但这个过程是比较耗费资源的。我有各种排序的数据结构,里面包含这种类型的对象。

当我对其中一个对象做某些操作时,我知道比较的顺序发生了变化,而在我的数据结构中(所有的数据结构!)可以通过将修改过的对象与“下一个更大的”对象交换来恢复这个顺序。

4 个回答

1

这个交换操作应该在你的排序数据结构中实现,而不是在这个类的实例里。

9

编辑

你现在做的事情是可以实现的,不过可能会让人觉得有点别扭,因为这有点像是“黑科技”。如果有可能的话,我建议你考虑重写或者调整一下你的比较操作符。这样做会让你得到更好的结果。当然,由于我不知道你具体的情况和时间限制,很难判断这样做是否立刻可行,但相信我,如果你能把事情做好,长远来看你会花更少的时间去重写代码。

原文

实际上,听起来你正在处理三个类——一个数据对象和两个工具类——但这又是另一个问题。

这样做是会出问题的,所以我得说,“不,你不能通过交换 __dict__ 来交换类”:

>>> class Foo:
...    def __init__(self):
...        self.__bar = 1
...    def printBar(self):
...        print self.__bar
... 
>>> class Bar:
...    def __init__(self):
...        self.__bar=2
...    def printBar(self):
...        print self.__bar
... 
>>> f=Foo()
>>> f.printBar() # works as expected
1
>>> f=Foo()
>>> b=Bar()
>>> f.__dict__, b.__dict__ = b.__dict__, f.__dict__
>>> f.printBar() # attempts to access private value from another class
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in printBar
AttributeError: Foo instance has no attribute '_Foo__bar'
7

如果我理解得没错,你其实是在交换一个类的实例,而不是类本身。

实例的状态可以保存在两个地方:__slots____dict__。如果你交换了这两个地方的内容,实际上就是在交换实例,同时保留了原来的名称绑定。需要注意的是,这个类不能是不可变的(也就是不能定义 __hash__()),因为如果有实例已经是集合中的成员或者字典的键,那它们就会变得无法找回。

如果是我,我会把 .swap() 方法改成类方法,这样看起来会更简单易懂:

class SomeBigClass():
    @staticmethod
    def swap_dict(instance_a, instance_b):
        instance_a.__dict__, instance_b.__dict__ = \
                instance_b.__dict__, instance_a.__dict__
    @classmethod
    def swap_slot(cls, instance_a, instance_b):
        values = []
        for attr in cls.__slots__:
            values.append(
                (attr,
                 getattr(instance_a, attr),
                 getattr(instance_b, attr),
                 ))
        for attr, val1, val2 in values:
            setattr(instance_a, attr, val2)
            setattr(instance_b, attr, val1)

然后在后面

SomeBigClass.swap_dict(this_instance, other_instance)

或者

SomeBigClass.swap_slot(this_instance, other_instance)

为什么不应该这样做呢?如果你有实例绑定了名称,就不应该这样做。想想看:

frubbah = SomeBigClass(attr="Hi, Mom!")
zikroid = SomeBigClass(attr='Hi, Dad!")
SomeBigClass.swap_dict(frubbah, zikroid)

在交换之后,你可能对 zikroid 的所有了解都发生了变化。

撰写回答