Re: [Python-ideas] Augmented assignment syntax for objects.
On Mon, Apr 24, 2017 at 6:08 PM, Erikwrote: > Hi. I suspect that this may have been discussed to death at some point in > the past, but I've done some searching and I didn't come up with much. > Apologies if I'm rehashing an old argument ;) > > 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 isn't a direct response, but you might be interested in the attrs library: https://attrs.readthedocs.io/en/stable/examples.html -n -- Nathaniel J. Smith -- https://vorpus.org ___ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
Re: [Python-ideas] Augmented assignment syntax for objects.
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
Re: [Python-ideas] Augmented assignment syntax for objects.
On Tue, Apr 25, 2017 at 11:08 AM, Erikwrote: > The suggestion therefore is: > > def __init__(self, foo, bar, baz, spam, ham): > self .= foo, bar, baz, spam, ham > > 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"). Bikeshedding: Your example looks a lot more like tuple assignment than multiple assignment. I'd rather it link more with the way that multiple assignment works: # simple multiple assignment a = b = c = d = e = 0 # object member assignment self .= foo .= bar .= baz .= spam .= ham The trouble is that this syntax is really only going to be used inside __init__. It's hard to justify new syntax for one purpose like this. So I'm swaying between -0 and +0.5 on this. ChrisA ___ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Augmented assignment syntax for objects.
Hi. I suspect that this may have been discussed to death at some point in the past, but I've done some searching and I didn't come up with much. Apologies if I'm rehashing an old argument ;) 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. 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 - though in practice, I'd probably not do that with a list quite that long ... two or three items at most: def __init__(self, foo, bar, baz): self.foo, self.bar, self.baz = foo, bar, baz 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. So why not have an augmented assignment operator for object attributes? 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 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"). The comma-separators in the example are not creating a tuple object, they would work at the same level in the parser as the import statement's comma-separated lists - in the same way that "from pkg import a, b, c" is the same as saying: import pkg a = pkg.a b = pkg.b c = pkg.c ... "self .= a, b, c" is the same as writing: self.a = a self.b = b self.c = c E. ___ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
Re: [Python-ideas] Binary arithmetic does not always call subclasses first
If this is portant I should probably ponder it. On Apr 24, 2017 4:47 PM, "Stephan Hoyer"wrote: > +Georg Brandl, in case he remembers where "Move the 3k reST doc tree in > place." moved things from: > https://github.com/python/cpython/commit/116aa62bf54a39697e25f21d6cf679 > 9f7faa1349 > > On Mon, Apr 24, 2017 at 4:29 PM, Nick Timkovich > wrote: > >> GitHub shows that that note hasn't changed in 10 years: >> https://github.com/python/cpython/blame/master/Doc/ >> reference/datamodel.rst#L2210 >> >> On Mon, Apr 24, 2017 at 3:15 PM, Terry Reedy wrote: >> >>> On 4/24/2017 12:14 PM, Stephan Hoyer wrote: >>> >>> Based on the change in the documentation between 2.x and 3.x, I wonder if this is something that someone intended to clean up as part of Python 3000 but never got around to. I would love to hear from anyone familiar with the historical context here. >>> >>> We should ask the intention of the person who made the change, which is >>> in the repository. If you email me the doc (the last part of the url) and >>> location within, I will look it up. >>> >>> -- >>> Terry Jan Reedy >>> >>> >>> ___ >>> Python-ideas mailing list >>> Python-ideas@python.org >>> https://mail.python.org/mailman/listinfo/python-ideas >>> Code of Conduct: http://python.org/psf/codeofconduct/ >>> >> >> >> ___ >> Python-ideas mailing list >> Python-ideas@python.org >> https://mail.python.org/mailman/listinfo/python-ideas >> Code of Conduct: http://python.org/psf/codeofconduct/ >> >> > > ___ > Python-ideas mailing list > Python-ideas@python.org > https://mail.python.org/mailman/listinfo/python-ideas > Code of Conduct: http://python.org/psf/codeofconduct/ > > ___ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
Re: [Python-ideas] Binary arithmetic does not always call subclasses first
+Georg Brandl, in case he remembers where "Move the 3k reST doc tree in place." moved things from: https://github.com/python/cpython/commit/116aa62bf54a39697e25f21d6cf6799f7faa1349 On Mon, Apr 24, 2017 at 4:29 PM, Nick Timkovichwrote: > GitHub shows that that note hasn't changed in 10 years: > https://github.com/python/cpython/blame/master/ > Doc/reference/datamodel.rst#L2210 > > On Mon, Apr 24, 2017 at 3:15 PM, Terry Reedy wrote: > >> On 4/24/2017 12:14 PM, Stephan Hoyer wrote: >> >> Based on the change in the documentation between 2.x and 3.x, I wonder if >>> this is something that someone intended to clean up as part of Python 3000 >>> but never got around to. I would love to hear from anyone familiar with the >>> historical context here. >>> >> >> We should ask the intention of the person who made the change, which is >> in the repository. If you email me the doc (the last part of the url) and >> location within, I will look it up. >> >> -- >> Terry Jan Reedy >> >> >> ___ >> Python-ideas mailing list >> Python-ideas@python.org >> https://mail.python.org/mailman/listinfo/python-ideas >> Code of Conduct: http://python.org/psf/codeofconduct/ >> > > > ___ > Python-ideas mailing list > Python-ideas@python.org > https://mail.python.org/mailman/listinfo/python-ideas > Code of Conduct: http://python.org/psf/codeofconduct/ > > ___ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
Re: [Python-ideas] Binary arithmetic does not always call subclasses first
GitHub shows that that note hasn't changed in 10 years: https://github.com/python/cpython/blame/master/Doc/reference/datamodel.rst#L2210 On Mon, Apr 24, 2017 at 3:15 PM, Terry Reedywrote: > On 4/24/2017 12:14 PM, Stephan Hoyer wrote: > > Based on the change in the documentation between 2.x and 3.x, I wonder if >> this is something that someone intended to clean up as part of Python 3000 >> but never got around to. I would love to hear from anyone familiar with the >> historical context here. >> > > We should ask the intention of the person who made the change, which is in > the repository. If you email me the doc (the last part of the url) and > location within, I will look it up. > > -- > Terry Jan Reedy > > > ___ > Python-ideas mailing list > Python-ideas@python.org > https://mail.python.org/mailman/listinfo/python-ideas > Code of Conduct: http://python.org/psf/codeofconduct/ > ___ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
Re: [Python-ideas] Binary arithmetic does not always call subclasses first
On 4/24/2017 12:14 PM, Stephan Hoyer wrote: Based on the change in the documentation between 2.x and 3.x, I wonder if this is something that someone intended to clean up as part of Python 3000 but never got around to. I would love to hear from anyone familiar with the historical context here. We should ask the intention of the person who made the change, which is in the repository. If you email me the doc (the last part of the url) and location within, I will look it up. -- Terry Jan Reedy ___ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
Re: [Python-ideas] Binary arithmetic does not always call subclasses first
On Mon, Apr 24, 2017 at 05:57:17PM +1200, Greg Ewing wrote: > Stephan Hoyer wrote: > >In practice, CPython requires that the > >right operand defines a different method before it defers to it. > > I'm not sure exactly what the rationale for this behaviour is, > but it's probably something along the lines that the left > method should already know how to deal with that combination > of types, and right methods are only supposed to be called > as a fallback if the left method can't handle the operands, > so calling it in that situation would be wrong. I've never seen that rationale before, and I don't think I would agree with it. And it goes against the rationale in the docs: [...] the right operand’s __rop__() method is tried before the left operand’s __op__() method. This is done so that a subclass can completely override binary operators. Otherwise, the left operand’s __op__() method would always accept the right operand: when an instance of a given class is expected, an instance of a subclass of that class is always acceptable. I think your rationale goes against the intention as documented. There's no expectation that __rop__ methods are only to be called when the __op__ method can't handle the operands. In general, which operand "wins" should depend on the classes, not on whether they happen to be on the left or right of the operator. (Except in the case where we cannot decide between the operands, in which case we break ties by preferring the __op__.) The reason is that subclasses are usually intended to be more specialised than their parent, and so they ought to be given priority in mixed operations. Given classes X, Y(X), with instances x and y, we should expect that the more specialised class (namely Y) gets called first whether we write: x ⊕ y or y ⊕ x for any operator ⊕. As documented in the 3 docs, we get that for free: the interpreter correctly calls the __op__ or __rop__ method, as needed, and the class author doesn't have to think about it. That's how it's documented, but not how it's implemented. The alternative is that every class has to include boilerplate testing for subclasses, as you say: > Following that logic, the wrapper's __add__ method in your > example needs to allow for the subclassing case, e.g. > >def __add__(self, other): >t1 = type(self) >t2 = type(other) >t = t2 if issubclass(t2, t1) else t1 >return t(self.value + other.value) but that's bad. That makes each and every class (that might ever be subclassed) responsible for checking for subclasses, instead of putting the check in one place (whichever part of the interpreter handles calling __op__/__rop__ methods). Remember that a specialised subclass might not overload the __op__ and __rop__ methods themselves. It might overload a data attribute, or another method that __op__ / __rop__ call. class A: def __add__(self, other): self.log() ... __radd__ = __add__ class B(A): def log(self): ... A() + B() As the more specialised instance (a subclass of A), the right hand operand should get the priority. > >the behavior is different for comparisons, which defer to > >subclasses regardless of whether they implement a new method > > Comparisons are a bit different, because they don't have > separate left and right methods, although it's hard to see > exactly how that affects the logic. It doesn't affect the logic, and comparisons implement exactly the documented (in 3) behaviour. The only difference is that the reversed methods aren't spelled __rop__: __eq__ and __ne__ are their own reflection; __lt__ and __gt__ __le__ and __ge__ For example: py> class A(object): ... def __lt__(self, other): ... print("lt", self) ... return True ... def __gt__(self, other): ... print("gt", self) ... return False ... py> class B(A): ... pass ... py> A() < B() gt <__main__.B object at 0xb7a9e8ec> False The more specialised class (B) has its method called, even though it isn't over-ridden. -- Steve ___ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
Re: [Python-ideas] Binary arithmetic does not always call subclasses first
On Sun, Apr 23, 2017 at 10:57 PM, Greg Ewingwrote: > Stephan Hoyer wrote: > >> In practice, CPython requires that the right operand defines a different >> method before it defers to it. >> > > I'm not sure exactly what the rationale for this behaviour is, > but it's probably something along the lines that the left > method should already know how to deal with that combination > of types, and right methods are only supposed to be called > as a fallback if the left method can't handle the operands, > so calling it in that situation would be wrong. > >> Yes, this could makes sense. But in that case, why check for explicitly overridden methods on subclasses at all? I can rationalize either not treating subclasses differently or always trying subclasses first, but not the current behavior. Of these two options, I prefer always trying subclasses first because I agree with the rationale in the docs: "This behavior allows subclasses to override their ancestors’ operations." In general, code should be written such that subclasses are aware of super-classes, not the other way around. > The 3.x docs don't have the "and overrides" language; >> > > so arguably we would be changing the implementation to match > the docs. > Based on the change in the documentation between 2.x and 3.x, I wonder if this is something that someone intended to clean up as part of Python 3000 but never got around to. I would love to hear from anyone familiar with the historical context here. Nonetheless, at this point the behavior has been around for quite some time. Almost assuredly, there is *someone* relying on this feature/bug, though probably unintentionally. So I would also love to hear from anyone who knows of code that this change would actually break (as opposed to fix and/or allow for removing redundant methods). More broadly: is this change significant enough that it needs a PEP? ___ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/