可以通过交换__dict__高效地交换两个类实例吗?
我有一个很大的类,里面有很多成员,而且有不少地方引用了这个类的实例。可惜的是(出于合理的原因),这些引用的方向都是错的。
我不想重新创建每个实例(这样还得到处找并更新所有引用的地方),也不想每次访问这个类时都增加一个额外的间接层,或者一个个地交换成员,所以我定义了一个方法:
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 个回答
这个交换操作应该在你的排序数据结构中实现,而不是在这个类的实例里。
编辑
你现在做的事情是可以实现的,不过可能会让人觉得有点别扭,因为这有点像是“黑科技”。如果有可能的话,我建议你考虑重写或者调整一下你的比较操作符。这样做会让你得到更好的结果。当然,由于我不知道你具体的情况和时间限制,很难判断这样做是否立刻可行,但相信我,如果你能把事情做好,长远来看你会花更少的时间去重写代码。
原文
实际上,听起来你正在处理三个类——一个数据对象和两个工具类——但这又是另一个问题。
这样做是会出问题的,所以我得说,“不,你不能通过交换 __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'
如果我理解得没错,你其实是在交换一个类的实例,而不是类本身。
实例的状态可以保存在两个地方:__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 的所有了解都发生了变化。