Reputation: 145
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
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
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
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
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