tags:

views:

110

answers:

4

Suppose I have in python this object

class Foo:  
    def __init__(self, val):
        self.val = val

and these two variables

a=Foo(5)
b=a

both b and a refer to the same instance of Foo(), so any modification to the attribute .val will be seen equally and synchronized as a.val and b.val.

>>> b.val
5
>>> b.val=3
>>> a.val
3

Now suppose I want to say a=Foo(7). This will create another instance of Foo, so now a and b are independent.

My question is: is there a way to have b readdressed automatically to the new Foo() instance, without using an intermediate proxy object? It's clearly not possible with the method I presented, but maybe there's some magic I am not aware of.

+1  A: 

update:

I figured out how to make it work with tuples. This is basically Odomontois' solution with some type checking removed and made recursive for tuples.

import gc
import inspect

def update(obj, value):
    objects = gc.get_referrers(obj)
    old = obj # this protects our reference to the initial object
    for o in objects:
        if hasattr(o, '__iter__') and hasattr(o, 'extend'):  # list like objects
            new_list = [value if item is old else item for item in o]
            while o: del o[-1]
            o.extend(new_list)
        elif hasattr(o, '__iter__') and hasattr(o, 'keys'): # dictionary like objects   
            for item in o.keys():
                if o[item] is old: o[item] = value
        elif isinstance(o, set):
            o.remove(old)
            o.add(value)
        elif isinstance(o, tuple):
            new_tuple = tuple(value if item is old else item for item in o)
            update(o, new_tuple)
        elif inspect.isframe(o):
            continue
        else: 
            raise Exception("can't handle referrer {0}".format(o))           


class Test(object):
   def __init__(self, obj):
       self.val = obj

a = (1, )  #works
b = a   #works
t = Test(b)  #works because we this t.__dict__
L = [a]  # works
S = set((a, )) # works
T = (a, ) # works


update(a, (2, ))
print a, b, t.val, L, S, T

wrong answer

deleted

aaronasterling
As i can see your solution does not handle sets, lists and tuples too, but working much longer than mine.
Odomontois
@Odomontois good call. the solution is to add an `is in` check which i had edited out in hacking around with it.
aaronasterling
+2  A: 

Updated for aaronasterling:

for d in gc.get_referrers(old):
    if isinstance(d,list):
        new_list = [ new if item is old else item for item in d]
        while d: del d[-1]
        d.extend(new_list)
    elif isinstance(d,dict):
        try:
            for item in d:  
                if d[item] is old: d[item] = new
        except Exception:pass
    else: print('cant handle referrer at %i' % id(d))

Also you dont need to readdress reference to make it's instance equal to some other object. You can just write

new.__dict__ = old.__dict__
new.__class__ = old.__class__

But this will work only with not-built-in class instances.

Odomontois
that's badass :)
Stefano Borini
that doesn't even work. 'TypeError: frame is not iterable'
aaronasterling
That still doesn't work. the problem is with the `for` loop itself.
aaronasterling
@aaronasterling works good on 2.6, 2.7 and 3.1 in cases i can produce.
Odomontois
@Odomontois No it doesn't. Try putting the object in a list, set, tuple or, generally, anything that isn't a generalization of a dictionary. Also, If you assume that d is iterable, and it isn't, then your whole loop breaks before it completes.
aaronasterling
@aaronasterling can you paste your code somewhere?
Odomontois
@aaronasterling It's obvious there is object's like immutable tuples for example and we can't do there anything. But for object- and module-dicts it will work.
Odomontois
@Odomontois, It should at least work for a list and announce failure on a tuple. I pasted the code in the bottom of my answer
aaronasterling
Good call. I deleted my answer
aaronasterling
+7  A: 

As Aaron points out, there may be very hacky and fragile solutions but there is likely nothing that would be guaranteed to work across all Python implementations (e.g. CPython, IronPython, Jython, PyPy, etc). But why would one realistically want to do something that is so contrary to the design and idiomatic use of the language when there are simple idiomatic solutions available? Calling a class object typically returns an instance object. Names are bound to that object. Rather than trying to fight the language by coming up with hacks to track down references to objects in order to update bindings, the natural thing would be to design a mutable instance object, if necessary using a simple wrapper around an existing class.

Ned Deily
+1 when I can vote again. The correct thing to do here is do it because we can and then redesign our program so that we don't need to do it in the first place.
aaronasterling
@aaronasterling : why can't you vote ?
Stefano Borini
because I've voted too much today.
aaronasterling
@Ned : for teaching purposes I invented this need. It's not finalized to design, just for discussion.
Stefano Borini
@aaronasterling : oh right. I hit that limit so few times that I tend to forget that it exists.
Stefano Borini
@Stefano: wouldn't it be better to teach someone to think pythonically?
Ned Deily
@Ned : the point is that I am trying to teach myself not to. I'm messing up things as an experiment to understand other things in JavaScript, actually. Don't worry.. :)
Stefano Borini
A: 

Maybe and just maybe you're looking for a Singleton

class Singleton(type):
    def __init__(cls, name, bases, dict):
        super(Singleton, cls).__init__(name, bases, dict)
        cls.instance = None

    def __call__(cls, *args, **kw):
        if cls.instance is None:
            cls.instance = super(Singleton, cls).__call__(*args, **kw)

        return cls.instance

class MyClass(object):
    __metaclass__ = Singleton

print MyClass()
print MyClass()
razpeitia