
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