0

Consider the following piece of code

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

class Widget:
    def __init__(self, low=None, mid=None, high=None):
        self.low = low
        self.mid = mid
        self.high = high

widget = Widget(low=[Point(0, 1), Point(1, 2), Point(2, 3)],
                mid=[Point(3, 4), Point(4, 5), Point(5, 6)],
                high=[Point(6, 7), Point(7, 8), Point(8, 9)])

a, b, c = Point(11, 11), Point(12, 12), Point(13, 13)

Now I would like to alter the attributes of the widget instance. Each attribute has to be altered in a certain way. Specifically, let us consider the (simplified) example where the first element of widget.low needs to be set to a, the second element of widget.mid to b and the last element of widget.high to c. Since these operations are very similar I am tempted to write it in a nested fashion like so,

for attr, ix, value in (('low', 0, a), ('mid', 1, b), ('high', 2, c):
    getattr(widget, attr)[ix] = value

Now, this feels very naughty. Because, I am using getattr to set (part of) an attribute. In this the first answer states that setting attributes should be done by setattr. The above construction would then become something like,

for attr, ix, value in (('low', 0, a), ('mid', 1, b), ('high', 2, c):
    setattr(widget, attr, getattr(widget, attr)[:ix] + [value] + getattr(widget, attr)[ix+1:])

Wow! That is really ugly. I believe the result of these two loops is the same, for instance, as expected self.mid = [Point(3,4), Point(12, 12), Point(5,6)] . I am interested in the 'correct' (most pythonic) way to do this? I know 'flat is better than nested' and for this task I could write out three lines. But I am considering a situation where nesting can save a significant amount of duplication. Thanks in advance :)

vshas
  • 49
  • 1
  • 5
  • 1
    _setting attributes should be done by `setattr`_: Yes, but you _aren't_ setting an attribute. You're _modifying_ an element of the attribute, but the value of the attribute itself remains unchanged. There's nothing wrong with `getattr(widget, attr)[ix] = value`, but unless you have a whole lot of attributes that need to be changed, I would just explicitly write out those lines: `self.low[0] = a; self.mid[1] = b; self.high[-1] = c` – Pranav Hosangadi Apr 06 '22 at 19:40
  • Also, the result of both your loops is _not_ the same. In the first case, it _modifies_ the lists in-place, so if you have `somelist = [Point(...), ...]` and then use that as `widget = Widget(somelist, ...)`, then modifying `widget.low` will also modify `somelist`. In the second case, `widget.low` is set to a _completely new list_ which is created by stitching together the old list with the new value, and `somelist` would remain unchanged. – Pranav Hosangadi Apr 06 '22 at 19:43
  • @PranavHosangadi, thanks for your answer/comment. My brain was equating any modification to 'setting', thanks for straightening me out. I did know that the two loops do not exactly do the same thing, next time I will be more cautious before I will use the word 'same' on the stack :) – vshas Apr 06 '22 at 19:54

0 Answers0