Ian McGarry
Ian McGarry

Reputation: 145

Tying two variables together in Python

I understand that in Python variables point to objects so when you assign one to another they then both point to the same object. What I'd like to do is to make one variable change when the other one does. In the case I am working on a GUI. So I have a label with an attribute for its text. I'd like that attribute to be equal to an attribute in another class. At the moment I am doing it by using an intermediate function but it feels like there should be a more elegant way of doing it. So my way is effectively similar to the below:

class Label():
    def init():
        self.text = None
        self.gettext = None

    def display():
        if callable(self.gettext):
            self.text = self.gettext()
        else:
            self.text = self.gettext
        print(str(self.text))

class Anotherclass():
    def init():
        self.anattribute = "avaluethatchanges"    

mylabel = Label()
myclass = Anotherclass()

def gettheattribute():
    return myclass.anattribute

mylabel.gettext = gettheattribute

There will be lots of labels linked to lots of different classes. So what I would like to be able to do is just:

mylabel.gettext = myclass.anattribute

However, when myclass.anattribute gets changed - myclass.gettext doesn't. I understand why but is there another way of writing it so that it does - without creating the function?

Many thanks

EDIT: - Both classes will be used in other applications where one or the other might not exist so I can't hard code the relationship between them within the classes themselves.

Upvotes: 2

Views: 1668

Answers (4)

rcriii
rcriii

Reputation: 707

Sounds like you have some GUI element that you want to tie to an underlying model. Riffing on 0x5453's very good advice, what about the following:

    class Label():
        def __init__(self, text_source, source_attribute):
            self.text_source = text_source
            self.source_attribute = source_attribute

        @property    
        def text_from_source(self):
            return getattr(self.text_source, self.source_attribute)

        def display(self):
            print(str(self.text_from_source))

    class Anotherclass():
        def __init__():
            self.anattribute = "avaluethatchanges"

>>> A = Anotherclass()
>>> L = Label(A, "anattribute")
>>> L.display()
avaluethatchanges
>>> A.anattribute = 3.1415
>>> L.display()
3.1415

This does not let you change the attribute from within the label, but I'd prefer it that way.

Upvotes: 0

sytech
sytech

Reputation: 41041

Sounds like this might be a good use case for a property. Properties let you have getter/setters that work seamlessly like attributes. From the docs

[a property is] a succinct way of building a data descriptor that triggers function calls upon access to an attribute
...
The property() builtin helps whenever a user interface has granted attribute access and then subsequent changes require the intervention of a method.

mylabel = Label()

class MyClass(object):
    def __init__(self, some_label):
        self._anattribute = None
        self.label = some_label

    @property
    def anattribute(self):
        return self._anattribute

    @anattribute.setter
    def anattribute(self, value):
        self._anattribute = value # set the underlying value
        # do something else, too
        self.label.text = self._anattribute

So...

mylabel = Label()
myinstance = MyClass(mylabel)
myinstance.anattribute = 'foo'
mylabel.text == 'foo' # True

Storing self._anattribute is not strictly necessary, either. You could have the getter/setter access/modify self.label.text directly, if applicable.

Upvotes: 1

Brendan Abel
Brendan Abel

Reputation: 37549

The first thing I would say is that it's somewhat of an antipattern to duplicate the storage of data in two places, since it violates the DRY principle of software development.

Generally, with GUI designs like this, there's the concept of MVC, or Model, View, Controller.

It's a pretty large topic, but the general idea is that you create a model object to store all your data, and then all the other parts of the GUI -- the many Views that display the data, and the Controllers that change the data -- all look at the model, so that the data is only stored and updated in one place.

GUI elements are either designed to accept a model and refreshes are either manually triggered or there is some type of Listen/Callback/Event system to automatically trigger refreshes on the Views when the model changes. The specific way to handle that depends on the GUI framework you are using.

One simple way to implement this would be to create a model class that both classes share and use python properties and a callback registry to trigger updates.

class Model():

    def __init__(self, text):
        self._text = text
        self._callbacks = []

    def on_text_changed(callback):
        self._callbacks.append(callback)     

    @property
    def text(self):
        return self._text

    @text.setter
    def text(self, value):
        self._text = text
        for callback in self._callbacks:
            callback()

Then both other classes would need something like this

class Label():

    def __init__(self, model):
        self.model = model
        self.model.on_text_changed(self.refresh)

    def refresh(self):
        print(self.text)

    @property
    def text(self):
        return self.model.text

    @text.setter
    def text(self, value):
        self.model.text = value

Then you would create them like this

model = Model('The text')
label = Label(model)
another_class = AnotherClass(model)

label.text = 'This will update text on all classes'
another_class.text = 'So will this'
model.text = "And so will this.

Upvotes: 1

Joran Beasley
Joran Beasley

Reputation: 114028

class MyClass:
    def __init__(self,shared_dict): # use some mutable datatype (a dict works well)
       self.shared = shared_dict
    def __getattr__(self,item):
       return self.shared.get(item)

data = {'a':'hello','b':[1,2,3]}
c = MyClass(data)
print(c.a)    
data['a'] = 'world!'
print(c.a)

I guess ... this doesnt make much sense from a use case standpoint really ... there is almost guaranteed to be a better way to do whatever it is you are actually trying to do (probably involves notifying subscribers and updating variables)

see it in action here https://repl.it/repls/TestyGruesomeNumbers

Upvotes: 0

Related Questions