15

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?

3
  • So, you want to change what data is stored in the place where a is stored? I'll post an answer after lunch.. Commented Jul 26, 2013 at 9:02
  • 1
    @Schoolboy Yes. Basically I've put a simple object in a, and would like to (lazily) promote it to a more complex object b when the need arises. a and b have the same interface, so code using them should be able to handle this. Commented Jul 26, 2013 at 9:13
  • 1
    For reference, if anyone stumbles across this, Smalltalk has precisely this functionality --- the become: method. softwareengineering.stackexchange.com/questions/290908/… Commented Mar 1, 2023 at 20:09

6 Answers 6

4

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'))
Sign up to request clarification or add additional context in comments.

1 Comment

You should include a little more information in your post about what you're linking to.
2

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?

8 Comments

Well, the contents of y,z won't change, as they refer to what x was referring to earlier.. The only thing that's changed is what x refers to. (Correct me if I'm wrong, as I don't know C).
If you only want to change what the name x refers to, x = whatever is what you're after. If you want to magically replace the object x was referring to with a new object, that'd have to be visible everywhere that used the old object, including dicts and tuples.
@Schoolboy but that is what the OP asked for: to replace not just x but all other names referencing the same object. In case you hadn't realised, in Python 1 is an object and in the main Python implementation everything using the value 1 is simply referencing the same object.
I should maybe add that both the old and the new object have the exact same interface, and they are both mutable (but they are completely different internally). I could probably go around and replace them manually in all lists and properties they are stored, or I could create a proxy object (probably the most sane thing, and what I should do). But I'm curious if I could just sneakily replace the object.
@Marcin: Pyjack's replace_all_refs is best-effort. It doesn't work reliably on strings, and I doubt it works on anything untracked by the GC. I would be astounded if it was reliable in the presence of arbitrary C extensions.
|
2

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

Comments

1

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.

1 Comment

Good and well explained overview.
0

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

Comments

0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.