0

I want to set up a system where a method of one object is called whenever an attribute of a different object is modified, with the new value as an argument to the method. How would I accomplish something like that?

Edit: Code sample/clarification:

My code looks something like this:

class SharedAttribute(object):  #I use this as a container class to share attributes between multiple objects
    def __init__(self, value):
        self.value = value

class BaseObject(object):
    def __init__(self, **shared_attributes:
        for (key, value) in shared_attributes:
            if isinstance(value, SharedAttribute):
                setattr(self, key, value)

            else:
                setattr(self, key, SharedAttribute(value))

class MyObject(BaseObject):
    def __init__(self, value1, value2):
        BaseObject.__init__(self, value1, value2)

    def on_value_changed(self, new_value):
        #Do something here

    def incrementValue(self):
        self.value1.value += 1

And then it would be implemented like:

a = MyObject(value1 = 1)
b = MyObject(value1 = MyObject.value1)  #a and b are storing references to the same attribute, so when one changes it, it's changed for the other one too.

I want to set it up so if I were to call a.incrementValue(), b.on_value_changed would be called, with the SharedAttribute's new value as an argument. Is there a good way to do that?

  • Possible duplicate of [How does the @property decorator work?](http://stackoverflow.com/questions/17330160/how-does-the-property-decorator-work) – sobolevn Nov 15 '15 at 23:12
  • That doesn't sound remotely like a duplicate of this question. – chepner Nov 15 '15 at 23:34
  • 1
    @property is for calling a method within the same object, I want to call a method within a different object –  Nov 16 '15 at 00:17

2 Answers2

0

One way to do it is to use a manager style attribute similar to how Django uses ModelManager's (model.objects for example):

class Foo(object):
    def method_to_call(self, new_val):
        print('Hello from manager. Doing stuff with {}'.format(new_val))


class Bar(object):
    manager = Foo()

    def __init__(self, your_attr):
        self.your_attr = your_attr

    def __setattr__(self, key, val):
        if key == 'your_attr':
            self.manager.method_to_call(val)
        super(Bar, self).__setattr__(key, val)

And to use it:

>>> bar = Bar('some value')
Hello from manager. Doing stuff with some value
>>> bar.your_attr = 'A new value'
Hello from manager. Doing stuff with A new value

If you want use a specific instance of Foo to manage a specific instance of Bar you can modify __init__() to take an instance of Foo and use that. Since we are making a super() call here it's important to keep track of your inheritance. I don't know how your app is setup but if you are using multiple inheritance things can get weird.

kylieCatt
  • 10,672
  • 5
  • 43
  • 51
  • I'm not sure how well this would work for my specific case. `Bar` is actually created by the superclass of `Foo`, so the information about what method to call has to be passed as arguments to `Foo`'s `__init__`, and then from there to `Foo`'s superclass's `__init__`, and from there to the respective `Bar`s (Each `Bar` is just storing a single attribute, so there will be a bunch). I feel like that could get pretty complicated pretty quickly if `Foo` has a bunch of methods, each called by a different `Bar` –  Nov 16 '15 at 19:10
  • That's a pretty different question then what you originally asked. You are going to have post a code sample so we can see what your code is doing. From your description it sounds like there might be a design issue somewhere. – kylieCatt Nov 16 '15 at 20:07
0

If you're being specific about which attributes are being updated and you have special knowledge of those attributes -- IOW you don't need to know whenever every attribute of some object changes -- then you probably just want some kind of observer pattern by way of properties.

class A(object):
    def __init__(self, x):
        self._x = x
    @property
    def x(self):
        return self._x
    @x.setter
    def x(self, value):
        self._x = value
        whatever_else_you_wanted_to_happen()

Otherwise if you're trying to get generic (which you probably don't want) then you end up wrapping a class attribute instead of an instance attribute, which has its own pros and cons.

class B(object):
    pass

def wrapper(objmethod):
    def _inner(*args, **kwargs)
        some_other_thing_you_wanted_to_happen()
        return objmethod(*args, **kwargs)
    return _inner

B.__setattr__ = wrapper(B.__setattr__)

That's really sort of JavaScript-y prototype-y though, and so people that like Python looking like Python will probably get their cooking spoons out and whack you with them.

I think ultimately if you want to do this you'll want to just stick to decorators. The attribute accessors aren't exposed in a way that you can just 'hook in' without writing a getter/setter so you'll almost certainly want properties and decorators anyway.