Mike Dergance
Mike Dergance

Reputation: 63

Python class property update not propagating to parent class

When I update an instance property, that change is not reflected in the __str__ method of the parent class. This makes sense, as super().__init__ is called during the __init__ call, and when I make an update to the property, it's not getting called again.

class BulkData:
    def __init__(self, fields):
        self.fields = fields

    def __str__(self):
        return ''.join(str(f) for f in self.fields)

class Node(BulkData):
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z
        fields = [self.x, self.y, self.z]
        super().__init__(fields)

Example:

n1 = Node(1,2,3)
n1.x = 5
print(n1)
> '123'

I would like the result to be '523'.

Upvotes: 0

Views: 404

Answers (2)

chepner
chepner

Reputation: 530970

You can define actual Python properties that reflect the state of fields:

class BulkData:
    def __init__(self, fields, floats=None):
        if floats is None:
            floats = []
        self.fields = fields
        self.floats = floats
    
    def __str__(self):
        return ''.join([str(x) for x in self.fields])

class Node(BulkData):
    def __init__(self, x, y, z):
        super().__init__([x, y, z])

    @property
    def x(self):
        return self.fields[0]

    @x.setter(self, v):
        self.fields[0] = v

    @property
    def y(self):
        return self.fields[1]

    @y.setter(self, v):
        self.fields[1] = v

    @property
    def z(self):
        return self.fields[2]

    @z.setter(self, v):
        self.fields[2] = v

Instead of instance attributes, you define property instances that "wrap" a particular slot of the underlying fields attribute.


If you are thinking, that's a lot of code... you're right. Let's fix that.

class FieldReference(property):
    def __init__(self, pos):
        def getter(self):
            return self.fields[pos]

        def setter(self, v):
            self.fields[pos] = v

        super().__init__(getter, setter)


class Node(BulkData):
    def __init__(self, x, y, z):
        super().__init__([x, y, z])

    x = FieldReference(0)
    y = FieldReference(1)
    z = FieldReference(2)

Here, we subclass property and hard-code the getter and setter functions to operate on the position specified by the argument to the property. Note that self in getter and setter are distinct from self in __init__, but getter and setter are both closures with access to __init__'s argument pos.

You might also want to read the Descriptor how-to, specifically the section on properties to understand why FieldReference is defined the way it is.

Upvotes: 1

Tomerikoo
Tomerikoo

Reputation: 19404

You mention in your question "property" but those are just attributes. One way to achieve that is to actually use a property:

class BulkData:
    def __init__(self, fields):
        self.fields = fields

    def __str__(self):
        return ''.join(self.fields)

class Node(BulkData):
        def __init__(self, x, y, z):
            self._x = x
            self._y = y
            self._z = z
            fields = [self._x, self._y, self._z]
            super().__init__(fields)

        @property
        def x(self):
            return self._x

        @x.setter
        def x(self, val):
            self._x = val
            self.fields[0] = val

And now:

>>> n1 = Node('1','2','3')
>>> n1.x = '5'
>>> print(n1)
523

Upvotes: 2

Related Questions