开发者

Can I efficiently swap two class instances by swapping __dict__?

开发者 https://www.devze.com 2023-04-01 04:13 出处:网络
I have a big class with lots of members, and quite a few references to instances of this class lying around. Unfortunately (f开发者_JS百科or reasonable reasons) all these references are the wrong way

I have a big class with lots of members, and quite a few references to instances of this class lying around. Unfortunately (f开发者_JS百科or reasonable reasons) all these references are the wrong way around.

Instead of re-creating each (and finding and updating everywhere the objects are referenced), or adding an extra level of indirection each time I access this class, or individually swapping members, I have defined a method:

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

so I can do:

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

The question: It seems to work fine (I don't use __slots__), but it feels like there might be a reason I shouldn't do this, is there?


Here's my actual use case:

I have a type that implements comparison operators in a (necessarily) expensive way. I have various sorted data-structures containing objects of this type.

When I do something to one of the objects, I know that the comparison order has changed, and that order in my data structures (all of them!) can be restored by swapping the modified object with the 'next bigger' one.


Edit

What you're doing is possible, although it will make people cringe because it is hackish. If at all possible, I would suggest that you look at rewriting/refactoring your comparison operators. That will give you the best outcome by far. Of course, not knowing the scope or time-frame involved, it is very difficult to tell whether this is immediately practical, but trust me, you will spend less time re-writing in the long term if you can do things "right".

Original

Realistically, it sounds like you're dealing with three classes -- a data object and two utility classes -- but that is another issue.

This will break, so I am going to go ahead and say, "no, you cannot swap classes by swapping __dict__s":

>>> 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'


If I understand correctly, you are actually swapping instances of a class, not classes.

Instance state is kept in two possible places: __slots__ and __dict__. If you swap those, you've basically swapped instances while retaining the original name bindings. One caveat is that the class cannot be immutable (must not define __hash__()) as any instances that were already members of a set or keys in a dictionary would then become irretrievable.

If it were me, I think I would have the .swap() method be a class method instead -- I think it would read easier:

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)

and then later

SomeBigClass.swap_dict(this_instance, other_instance)

or

SomeBigClass.swap_slot(this_instance, other_instance)

Why shouldn't you do this? If you have instances bound to names you should not do this. Consider:

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

After the swap, potentially everything you thought you knew about zikroid has changed.


This swapping should be implemented in your sorted data structures, not in this class for its instances.


For your own sanity, and that of whoever may use your code after you, if you decide to use this function, you should document the hell out of it so that you know what's going on. Your more self-documenting alternative, of course, is to write a function that swaps all the data members individually. Obviously this isn't very future-proof if your data model will be changing, but it will be less confusing. If it's not something you want to write out by hand, you could define a function to swap one data field, and run it over all your class attributes:

def swap (a, b):
  for key in a.__dict__.keys():
    swapOneField(key, a, b)

and for swapOneField get the values from a.__dict__ and b.__dict__ and use the swapping algorithm of your choice. It's more verbose, and probably less efficient, but if you save more time in not debugging it than you lose in running it, it's probably worth your time.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号