Reputation: 33
I'm working with a legacy system for which a Python interface has been added recently. In my code, I get messages containing ASCII strings for attributes to be set in some wrapper classes. I would like to use a dictionary to map "data labels" to property setter methods. Each property setter would be used as a "callback" when the corresponding data label is encountered in a message.
Using explicit setters/getters, the essential logic looks like this:
class A():
def __init__(self):
self._x = 1.2
def get_x(self):
return self._x
def set_x(self, value):
self._x = value
myA = A()
myTable = {
'X' : myA.set_x,
}
label, value = get_message()
print(myA.get_x())
# label is 'X', value a float
myTable[label](value)
print(myA.get_x())
This works, but is a bit ugly. I would like to use the @property
decorator, but then I don't know how to reference the setter method in the dictionary.
I.e. the following doesn't work.
class B():
def __init__(self):
self._x = 1.2
@property
def x(self):
return self._x
@x.setter
def x(self, value):
self._x = value
myB = B()
myTable = {
'X' : myB.x
}
label, value = get_message()
print(myB.x)
# doesn't work as expected
myTable[label] = value
# no change
print(myB.x)
Of course, the reference to property myB.x
in the dictionary definition calls the getter, so a float value is associated to the 'X' key. The myTable[label] = value
assignment just replaces this value, it doesn't call the setter.
So, is there a way to get a reference to the property setter to insert in the dictionary and to later invoke as a "callback"?
I dug in reference information and this answer, but can't figure out a solution by myself.
Or, am I getting it wrong and I should follow a different path? (Suggestions welcome).
Upvotes: 3
Views: 1651
Reputation: 71
I'm coming to this several years late, but I have a similar situation, and building off of juanpa.arrivillaga's answer I came up with this to answer your follow-up question, which is maybe what you were really hoping for originally.
Basically, an instance of TestDevice can use its own class method and getattr to find and call the appropriate setter:
class TestDevice(object):
@classmethod
def set_property(cls, instance, property, value):
getattr(cls, property).fset(instance, value)
def __init__(self, x):
self.x = x
def apply_state(self, state):
for k, v in state.items():
self.set_property(self, k, v)
@property
def x(self):
return self._x
@x.setter
def x(self, v):
self._x = v
Which seems to do the trick:
>>> thing = TestDevice(5)
>>> thing.x
5
>>> new_state = {'x': 7}
>>> thing.apply_state(new_state)
>>> thing.x
7
Upvotes: 0
Reputation: 95948
To access the actual function, you have to access the property directly on the class, so:
In [1]: class B:
...: def __init__(self):
...: self._x = 1.2
...:
...: @property
...: def x(self):
...: return self._x
...:
...: @x.setter
...: def x(self, value):
...: self._x = value
...:
In [2]: B.x.fset
Out[2]: <function __main__.B.x(self, value)>
Since functions are descriptors, you can use their __get__
method to bind them and change them into a method:
In [4]: B.x.fset.__get__(b)(42)
In [5]: b.x
Out[5]: 42
So, something like:
In [6]: my_table = {'X':B.x.fset.__get__(b)}
In [7]: my_table['X']('foo')
In [8]: b.x
Out[8]: 'foo'
Upvotes: 4