jdm
jdm

Reputation: 10120

Replace one python object with another everywhere

How do I replace a python object everywhere with another object?

I have two classes, SimpleObject and FancyObject. I've created a SimpleObject, and have several references to it. Now I'd like to create a FancyObject, and make all those references point to the new object.

a = SimpleObject()
some_list.append(a)
b = FancyObject()

a = b is not what I want, it just changes what a points to. I read the following would work, but doesn't. I get an error "Attribute __dict__ is not writable":

a.__dict__ = b.__dict__

What I want is the equivalent of (pseudo-C):

*a = *b

I know this is hacky, but is there any way to accomplish this?

Upvotes: 15

Views: 14244

Answers (6)

Miguel Montes
Miguel Montes

Reputation: 154

I encountered the same need and I worked around it using this trick:

class Reference:

    directions = dict()

    @staticmethod
    def direct(reference): # -> Reference:
        return Reference.directions.get(reference, reference)

    @staticmethod
    def route(origin, destination) -> None:
        Reference.directions.update({origin: destination})
        return None

    def __init__(self) -> None:
        self._reference = self
        Reference.route(self, (lambda: self._reference))
        return None

    @property
    def reference(self): # -> Reference:
        return Reference.direct(self._reference)()

    @reference.setter
    def reference(self, obj) -> None:
        Reference.route(self._reference, (lambda: obj.reference))
        return None


class Example(Reference):

    def __init__(self) -> None:
        super().__init__()
        return None

Which satisfies that the following:

obj1 = Example()
obj2 = Example()
obj3 = obj1
obj4 = Example()
print(obj1.reference is obj3.reference)
print(obj1.reference is obj2.reference)
print(obj3.reference is obj2.reference)
print()
obj1.reference = obj2.reference
print(obj1.reference is obj2.reference)
print(obj3.reference is obj2.reference)
print()
obj2.reference = obj4.reference
print(obj1.reference is obj4.reference)
print(obj2.reference is obj4.reference)
print(obj3.reference is obj4.reference)

outputs:

True
False
False

True
True

True
True
True

Upvotes: 0

user2682863
user2682863

Reputation: 3218

PyJack has a function replace_all_refs that replaces all references to an object in memory.

An example from the docs:

>>> item = (100, 'one hundred')
>>> data = {item: True, 'itemdata': item}
>>> 
>>> class Foobar(object):
...     the_item = item
... 
>>> def outer(datum):
...     def inner():
...         return ("Here is the datum:", datum,)
...     
...     return inner
... 
>>> inner = outer(item)
>>> 
>>> print item
(100, 'one hundred')
>>> print data
{'itemdata': (100, 'one hundred'), (100, 'one hundred'): True}
>>> print Foobar.the_item
(100, 'one hundred')
>>> print inner()
('Here is the datum:', (100, 'one hundred'))

Calling replace_all_refs

>>> new = (101, 'one hundred and one')
>>> org_item = pyjack.replace_all_refs(item, new)
>>> 
>>> print item
(101, 'one hundred and one')
>>> print data
{'itemdata': (101, 'one hundred and one'), (101, 'one hundred and one'): True}
>>> print Foobar.the_item
(101, 'one hundred and one')
>>> print inner()
('Here is the datum:', (101, 'one hundred and one'))

Upvotes: 4

Mark Ransom
Mark Ransom

Reputation: 308402

Take advantage of mutable objects such as a list.

a = [SimpleObject()]
some_list.append(a)
b = FancyObject()
a[0] = b

Proof that this works:

class SimpleObject():
    def Who(self):
        print 'SimpleObject'

class FancyObject():
    def Who(self):
        print 'FancyObject'

>>> a = [SimpleObject()]
>>> a[0].Who()
SimpleObject
>>> some_list = []
>>> some_list.append(a)
>>> some_list[0][0].Who()
SimpleObject
>>> b = FancyObject()
>>> b.Who()
FancyObject
>>> a[0] = b
>>> some_list[0][0].Who()
FancyObject

Upvotes: 0

Marcin
Marcin

Reputation: 49856

You have a number of options:

  1. Design this in from the beginning, using the Facade pattern (i.e. every object in your main code is a proxy for something else), or a single mutable container (i.e. every variable holds a list; you can change the contents of the list through any such reference). Advantages are that it works with the execution machinery of the language, and is relatively easily discoverable from the affected code. Downside: more code.
  2. Always refer to the same single variable. This is one implementation of the above. Clean, nothing fancy, clear in code. I would recommend this by far.
  3. Use the debug, gc, and introspection features to hunt down every object meeting your criterion and alter the variables while running. The disadvantage is that the value of variables will change during code execution, without it being discoverable from the affected code. Even if the change is atomic (eliminating a class of errors), because this can change the type of a variable after the execution of code which determined the value was of a different type, introduces errors which cannot reasonably be anticipated in that code. For example

    a = iter(b) # will blow up if not iterable
    [x for x in b] # before change, was iterable, but between two lines, b was changed to an int.
    

More subtly, when discriminating between string and non-string sequences (because the defining feature of strings is that iterating them also yields strings, which are themselves iterable), when flattening a structure, code may be broken.

Another answer mentions pyjack which implements option 3. Although it may work, it has all of the problems mentioned. This is likely to be appropriate only in debugging and development.

Upvotes: 1

denz
denz

Reputation: 386

You can put that object in global namespace of separate module and than monkey patch it when you need.

objstore.py:

replaceable = object()

sample.py:

import objstore

b = object()

def isB():
     return objstore.replaceable is b

if __name__ == '__main__':
     print isB()#False
     objstore.replaceable = b
     print isB()#True

P.S. Rely on monkey patching is a symptom of bad design

Upvotes: 2

user2357112
user2357112

Reputation: 281538

There's no way. It'd let you mutate immutable objects and cause all sorts of nastiness.

x = 1
y = (x,)
z = {x: 3}
magic_replace(x, [1])
# x is now a list!
# The contents of y have changed, and z now has an unhashable key.

x = 1 + 1
# Is x 2, or [1, 1], or something stranger?

Upvotes: 2

Related Questions