[Python-ideas] Augmented assignment syntax for objects.

Steven D'Aprano steve at pearwood.info
Mon Apr 24 22:53:06 EDT 2017


On Tue, Apr 25, 2017 at 02:08:05AM +0100, Erik wrote:

> I often find myself writing __init__ methods of the form:
> 
> def __init__(self, foo, bar, baz, spam, ham):
>   self.foo = foo
>   self.bar = bar
>   self.baz = baz
>   self.spam = spam
>   self.ham = ham
> 
> This seems a little wordy and uses a lot of vertical space on the 
> screen. 

It does, and while both are annoying, in the grand scheme of things 
they're a very minor annoyance. After all, this is typically only an 
issue once per class, and not even every class, and vertical space is 
quite cheap. In general, the barrier for accepting new syntax is quite 
high, and "minor annoyance" generally doesn't reach it.

I'm completely with you about the annoyance factor here. It is 
especially annoying during the early stages of development when the 
__init__ method is evolving rapidly (parameters are being renamed, added 
or removed). One work-around is this pattern:

    def __init__(self, spam, ham, eggs, cheese, aardvark):
        vars(self).update(locals())
        del self.self
        ...


which is cute but I've never quite been brave enough to use it in 
production. But I don't think the annoyance factor is high enough, or 
common enough, to justify syntactic sugar to "fix" it.



> Occasionally, I have considered something like:
> 
> def __init__(self, foo, bar, baz, spam, ham):
>   self.foo, self.bar, self.baz, self.spam, self.ham = \
>      foo, bar, baz, spam, ham
> 
> ... just to make it a bit more compact
[...]
> When I do that I'm torn because I know it has a runtime impact to create 
> and unpack the implicit tuples and I'm also introducing a style 
> asymmetry in my code just because of the number of parameters a method 
> happens to have.

I'm not too worried about the performance impact unless I've 
profiled my code and it is actually significant.

As far as the style asymmetry, if we use your proposed syntax, that's 
introducing style asymmetry too.



> So why not have an augmented assignment operator for object attributes? 

Wrong question. Don't ask "why not add this feature?". The question to 
ask is "why add this feature?" Every new feature has cost, whether it is 
run-time cost, development cost, feature bloat, learning curve cost, 
etc, so the feature must be justified as adding sufficiently more 
value than it costs.

Here are a few reasons why this feature fails to meet the barrier for 
new syntax.

- This feature doesn't add any new capability to the language. There's 
nothing that this feature allows you to do which you couldn't do before.

- It has quite narrow applicability. It's really only useful inside a 
small proportion of __init__ methods.

- In my experience, it is very rare to have a whole set of unadorned 
assignments in the way you show it. MUCH more common is to have some 
sort of pre-processing of the argument before assignment, or for the 
assignment target to be different from the parameter name:


    def __init__(self, spam, eggs, cheese, aardvark, ...):
        # type checks
        if not isinstance(spam, Foo):
            raise TypeError
        self._spam = spam  # not all parameters are public attributes
        # value checks
        if eggs < 0:
            raise ValueError
        self.eggs = eggs
        # replacement of None
        if cheese is None:
            cheese = make_cheese()
        self.cheese = cheese
        # other pre-processing
        self.mammals = [aardvark, get_weasel()]


which reduces the applicability of this syntax even further.

- Having big parameter lists is something of a mild code smell. As the 
parameter list gets bigger, the smell gets worse. Having so many 
parameters that this feature becomes attractive should be painful, 
because its a warning that maybe your class has too many parameters.



> It addresses one of the same broad issues that the other augmented 
> assignment operators were introduced for (that of repeatedly spelling 
> names).
> 
> The suggestion therefore is:
> 
> def __init__(self, foo, bar, baz, spam, ham):
>   self .= foo, bar, baz, spam, ham

But there's a major difference between this and the other augmented 
assignment operators, so major that the analogy between them doesn't 
hold up.

In augmented assignment, the target can be any valid target, and the RHS 
can be any expression

    mylist[0].attr['key'] += (some_value() or another) + 99

Your suggestion is very different: there are *many* targets, and the RHS 
must be a comma-separated list of identifiers. They're quite different 
kinds of assignment. And unlike augmented assignment, which calls one of 
the __iop__ methods, this would have to be handled purely as syntax.

> This is purely syntactic sugar for the original example:
> 
> def __init__(self, foo, bar, baz, spam, ham):
>   self.foo = foo
>   self.bar = bar
>   self.baz = baz
>   self.spam = spam
>   self.ham = ham
> 
> ... so if any of the attributes have setters, then they are called as 
> usual. It's purely a syntactic shorthand. Any token which is not 
> suitable on the RHS of the dot in a standard "obj.attr =" assignment is 
> a syntax error (no "self .= 1").

Right. And that *seriously* limits the applicability of this.

Yes, I've written classes with a dozen or twenty parameters. But 
especially with long parameter lists, invariably at least *some* of 
those arguments are pre-processed before assignment to an attribute:

    self.spam = spam + 1

or are assigned to a different name:

    self._spam = spam

or both:

    self.foods = [spam, eggs, cheese]

So my expectation is that even if this syntax was accepted, I wouldn't 
use it, because by the time I've processed those assignments, the ones 
that are left are too few to bother with the special syntax.

The bottom line is that I think you've identified a very real, but 
minor, annoyance, but the applicability of the solution is too low to 
justify new syntax.



-- 
Steve


More information about the Python-ideas mailing list