How to use property setter as a callback

Question:

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).

Asked By: arabu

||

Answers:

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'
Answered By: juanpa.arrivillaga

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
Answered By: dirtbirb
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.