0

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

CrazyChucky
  • 3,263
  • 4
  • 11
  • 25
  • 2
    You changed `x` but not `fields`... Please read about [How to debug small programs](http://ericlippert.com/2014/03/05/how-to-debug-small-programs/). You can also use [Python-Tutor](http://www.pythontutor.com/visualize.html#mode=edit) which helps to visualize the execution of the code step-by-step. – Tomerikoo Dec 22 '20 at 21:33
  • 1
    `fields` is a list initialized from the *values* of `x`, `y`, and `z`; it is not otherwise related to the `Node` attributes. – chepner Dec 22 '20 at 21:35
  • 1
    Also, in this case the code's simple enough we can tell what you mean, but please keep in mind that it's a good idea to test your code first and make sure it actually does what you think it does before posting it here. Even after fixing the indentation, this will throw an exception when trying to reference `floats`, and `print(Node)` is not `print(n1)`. – CrazyChucky Dec 22 '20 at 21:38

2 Answers2

2

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
Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
1

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.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • Slick! But what's the `None` in `Node`'s call to `super().__init__`? (Also keep in mind that `floats` is undefined, and that `str.join` will fail on integers. I know it was the same way in OP's code, but still.) – CrazyChucky Dec 22 '20 at 21:55
  • 1
    Oh, I just stuck something in there to make sure the code ran. It should presumably be whatever hard-coded value you want for the `floats` attribute defined in `BulkData.__init__`. – chepner Dec 22 '20 at 22:03
  • 1
    I also forgot to update the answer to make sure `join` got a list of strings; will fix. – chepner Dec 22 '20 at 22:04