PEP 505 (None coalescing operators) thoughts
![](https://secure.gravatar.com/avatar/ad24799da38da64c3b51b745087d60fd.jpg?s=120&d=mm&r=g)
TL;DR: +1 for the idea -1 on the propagating member-access or index operators +1 on spelling it "or?" C# has had null-coalescing since about 2005, and it's one feature I miss in every other language that I use. I view null/None as a necessary evil, so getting rid of them as soon possible is a good thing in my book. Nearly every bit of Python I've ever written would have benefitted from it, if just to get rid of the "x if x is not None else []" mess. That said, I think the other (propagating) operators are a mistake, and I think they were a mistake in C# as well. I'm not I've ever had a situation where I wished they existed, in any language. Better to get rid of the Nones as soon as possible than bring them along. It's worth reading the C# design team's notes and subsequent discussion on the associativity of "?." [1] since it goes around and around with no really good answer and no particularly intuitive behaviour. Rather than worry about that, I'd prefer to see just the basic None-coalescing added. I like Alessio's suggestion of "or?" (which seems like it should be read in a calm but threatening tone, a la Liam Neeson). It just seems more Pythonic; ?? is fine in C# but seems punctuation-heavy for Python. It does mean the ?= and ?. and ?[] are probably out, and I'm OK with that. - Jeff [1] https://roslyn.codeplex.com/discussions/543895
![](https://secure.gravatar.com/avatar/130fe9f08ce5d2b1716d32438a58c867.jpg?s=120&d=mm&r=g)
On 28.09.2015 18:02, Jeff Hardy wrote:
TL;DR: +1 for the idea -1 on the propagating member-access or index operators +1 on spelling it "or?"
C# has had null-coalescing since about 2005, and it's one feature I miss in every other language that I use. I view null/None as a necessary evil, so getting rid of them as soon possible is a good thing in my book. Nearly every bit of Python I've ever written would have benefitted from it, if just to get rid of the "x if x is not None else []" mess.
That said, I think the other (propagating) operators are a mistake, and I think they were a mistake in C# as well. I'm not I've ever had a situation where I wished they existed, in any language. Better to get rid of the Nones as soon as possible than bring them along. It's worth reading the C# design team's notes and subsequent discussion on the associativity of "?." [1] since it goes around and around with no really good answer and no particularly intuitive behaviour.
Rather than worry about that, I'd prefer to see just the basic None-coalescing added. I like Alessio's suggestion of "or?" (which seems like it should be read in a calm but threatening tone, a la Liam Neeson). It just seems more Pythonic; ?? is fine in C# but seems punctuation-heavy for Python. It does mean the ?= and ?. and ?[] are probably out, and I'm OK with that.
- Jeff
That sums it all up for me as well, though I would rather use "else" instead of "or?" (see punctuation-heavy). Best, Sven
![](https://secure.gravatar.com/avatar/5615a372d9866f203a22b2c437527bbb.jpg?s=120&d=mm&r=g)
On Mon, Sep 28, 2015 at 06:11:45PM +0200, Sven R. Kunze wrote:
That sums it all up for me as well, though I would rather use "else" instead of "or?" (see punctuation-heavy).
`else` is ambiguous. Consider: result = spam if eggs else cheese else aardvark could be interpreted three ways: result = (spam if eggs else cheese) else aardvark result = spam if (eggs else cheese) else aardvark result = spam if eggs else (cheese else aardvark) Whichever precedence you pick, some people will get it wrong and it will silently do the wrong thing and lead to hard-to-diagnose bugs. Using "else" for this will be a bug-magnet. -- Steve
![](https://secure.gravatar.com/avatar/ebf132362b622423ed5baca2988911b8.jpg?s=120&d=mm&r=g)
could use two words! result = spam or else eggs On September 28, 2015 at 12:38:21 PM, Steven D'Aprano (steve@pearwood.info) wrote:
On Mon, Sep 28, 2015 at 06:11:45PM +0200, Sven R. Kunze wrote:
That sums it all up for me as well, though I would rather use "else" instead of "or?" (see punctuation-heavy).
`else` is ambiguous. Consider:
result = spam if eggs else cheese else aardvark
could be interpreted three ways:
result = (spam if eggs else cheese) else aardvark result = spam if (eggs else cheese) else aardvark result = spam if eggs else (cheese else aardvark)
Whichever precedence you pick, some people will get it wrong and it will silently do the wrong thing and lead to hard-to-diagnose bugs. Using "else" for this will be a bug-magnet.
-- 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/
----------------- Donald Stufft PGP: 0x6E3CBCE93372DCFA // 7C6B 7C5D 5E2B 6356 A926 F04F 6E3C BCE9 3372 DCFA
![](https://secure.gravatar.com/avatar/7e41acaa8f6a0e0f5a7c645e93add55a.jpg?s=120&d=mm&r=g)
On Sep 28, 2015, at 09:40, Donald Stufft <donald@stufft.io> wrote:
could use two words!
result = spam or else eggs
Unless you change the tokenizer to understand "or else" as a special case, or add another level of lookahead to the parser, how do you handle "spam if eggs or else cheese else aardvark" and vice-versa? It does make the meaning less confusing to a human, but it makes understanding how the compiler parses that meaning harder to understand to a human.
On September 28, 2015 at 12:38:21 PM, Steven D'Aprano (steve@pearwood.info) wrote:
On Mon, Sep 28, 2015 at 06:11:45PM +0200, Sven R. Kunze wrote:
That sums it all up for me as well, though I would rather use "else" instead of "or?" (see punctuation-heavy).
`else` is ambiguous. Consider:
result = spam if eggs else cheese else aardvark
could be interpreted three ways:
result = (spam if eggs else cheese) else aardvark result = spam if (eggs else cheese) else aardvark result = spam if eggs else (cheese else aardvark)
Whichever precedence you pick, some people will get it wrong and it will silently do the wrong thing and lead to hard-to-diagnose bugs. Using "else" for this will be a bug-magnet.
-- 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/
----------------- Donald Stufft PGP: 0x6E3CBCE93372DCFA // 7C6B 7C5D 5E2B 6356 A926 F04F 6E3C BCE9 3372 DCFA
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
![](https://secure.gravatar.com/avatar/c2c59de363748beec0262699f1e8d0d5.jpg?s=120&d=mm&r=g)
On Sep 28, 2015, at 9:40 AM, Donald Stufft <donald@stufft.io> wrote:
could use two words!
result = spam or else eggs
Could use otherwise: result = spam otherwise eggs
On September 28, 2015 at 12:38:21 PM, Steven D'Aprano (steve@pearwood.info) wrote:
On Mon, Sep 28, 2015 at 06:11:45PM +0200, Sven R. Kunze wrote:
That sums it all up for me as well, though I would rather use "else" instead of "or?" (see punctuation-heavy).
`else` is ambiguous. Consider:
result = spam if eggs else cheese else aardvark
could be interpreted three ways:
result = (spam if eggs else cheese) else aardvark result = spam if (eggs else cheese) else aardvark result = spam if eggs else (cheese else aardvark)
Whichever precedence you pick, some people will get it wrong and it will silently do the wrong thing and lead to hard-to-diagnose bugs. Using "else" for this will be a bug-magnet.
-- 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/
----------------- Donald Stufft PGP: 0x6E3CBCE93372DCFA // 7C6B 7C5D 5E2B 6356 A926 F04F 6E3C BCE9 3372 DCFA
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
![](https://secure.gravatar.com/avatar/130fe9f08ce5d2b1716d32438a58c867.jpg?s=120&d=mm&r=g)
On 28.09.2015 18:37, Steven D'Aprano wrote:
On Mon, Sep 28, 2015 at 06:11:45PM +0200, Sven R. Kunze wrote:
That sums it all up for me as well, though I would rather use "else" instead of "or?" (see punctuation-heavy). `else` is ambiguous. Consider:
result = spam if eggs else cheese else aardvark
could be interpreted three ways:
result = (spam if eggs else cheese) else aardvark result = spam if (eggs else cheese) else aardvark result = spam if eggs else (cheese else aardvark)
Whichever precedence you pick, some people will get it wrong and it will silently do the wrong thing and lead to hard-to-diagnose bugs. Using "else" for this will be a bug-magnet.
I wouldn't make a mountain out of a molehill. Other existing operators have the same issue. Best, Sven
![](https://secure.gravatar.com/avatar/7e41acaa8f6a0e0f5a7c645e93add55a.jpg?s=120&d=mm&r=g)
On Sep 28, 2015, at 09:47, Sven R. Kunze <srkunze@mail.de> wrote:
On 28.09.2015 18:37, Steven D'Aprano wrote:
On Mon, Sep 28, 2015 at 06:11:45PM +0200, Sven R. Kunze wrote:
That sums it all up for me as well, though I would rather use "else" instead of "or?" (see punctuation-heavy). `else` is ambiguous. Consider:
result = spam if eggs else cheese else aardvark
could be interpreted three ways:
result = (spam if eggs else cheese) else aardvark result = spam if (eggs else cheese) else aardvark result = spam if eggs else (cheese else aardvark)
Whichever precedence you pick, some people will get it wrong and it will silently do the wrong thing and lead to hard-to-diagnose bugs. Using "else" for this will be a bug-magnet.
I wouldn't make a mountain out of a molehill. Other existing operators have the same issue.
Which other keywords or symbols may be either a binary operator or part of a ternary operator depending on context?
Best, Sven _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
![](https://secure.gravatar.com/avatar/4655830e7145e4fbe09aca7a38589721.jpg?s=120&d=mm&r=g)
On 9/28/2015 10:24 AM, Andrew Barnert via Python-ideas wrote:
On Sep 28, 2015, at 09:47, Sven R. Kunze <srkunze@mail.de> wrote: <snip>
I wouldn't make a mountain out of a molehill. Other existing operators have the same issue.
Which other keywords or symbols may be either a binary operator or part of a ternary operator depending on context?
These come to mind: a = b = c a < b < c Emile
![](https://secure.gravatar.com/avatar/d6b9415353e04ffa6de5a8f3aaea0553.jpg?s=120&d=mm&r=g)
On 9/28/2015 3:38 PM, Emile van Sebille wrote:
On 9/28/2015 10:24 AM, Andrew Barnert via Python-ideas wrote:
On Sep 28, 2015, at 09:47, Sven R. Kunze <srkunze@mail.de> wrote: <snip>
I wouldn't make a mountain out of a molehill. Other existing operators have the same issue.
Which other keywords or symbols may be either a binary operator or part of a ternary operator depending on context?
These come to mind:
a = b = c a < b < c
These are chained comparisons, which get separated, not ternary operators. a < b = c < e > f in g is also syntactically valid, and I don't think anything is gained by calling it a pentanary operator. -- Terry Jan Reedy
![](https://secure.gravatar.com/avatar/047f2332cde3730f1ed661eebb0c5686.jpg?s=120&d=mm&r=g)
On Mon, Sep 28, 2015 at 3:40 PM, Terry Reedy <tjreedy@udel.edu> wrote:
On 9/28/2015 3:38 PM, Emile van Sebille wrote:
On 9/28/2015 10:24 AM, Andrew Barnert via Python-ideas wrote:
On Sep 28, 2015, at 09:47, Sven R. Kunze <srkunze@mail.de> wrote:
<snip>
I wouldn't make a mountain out of a molehill. Other existing
operators have the same issue.
Which other keywords or symbols may be either a binary operator or part of a ternary operator depending on context?
These come to mind:
a = b = c a < b < c
These are chained comparisons, which get separated, not ternary operators. a < b = c < e > f in g is also syntactically valid, and I don't think anything is gained by calling it a pentanary operator.
But a < b < c is an excellent example of something that cannot be mindlessly refactored into (a < b) < c. -- --Guido van Rossum (python.org/~guido)
![](https://secure.gravatar.com/avatar/130fe9f08ce5d2b1716d32438a58c867.jpg?s=120&d=mm&r=g)
On 28.09.2015 19:24, Andrew Barnert wrote:
On Sep 28, 2015, at 09:47, Sven R. Kunze <srkunze@mail.de> wrote:
result = (spam if eggs else cheese) else aardvark result = spam if (eggs else cheese) else aardvark result = spam if eggs else (cheese else aardvark)
Whichever precedence you pick, some people will get it wrong and it will silently do the wrong thing and lead to hard-to-diagnose bugs. Using "else" for this will be a bug-magnet.
I wouldn't make a mountain out of a molehill. Other existing operators have the same issue.
Which other keywords or symbols may be either a binary operator or part of a ternary operator depending on context?
It has nothing to do with either of it. I've seen young students struggling with the op precedence of AND and OR; and I've seen experienced coworkers rather adding superfluous pairs of parentheses just to make sure or because they still don't know better. Best, Sven
![](https://secure.gravatar.com/avatar/5dde29b54a3f1b76b2541d0a4a9b232c.jpg?s=120&d=mm&r=g)
On Mon, Sep 28, 2015 at 12:47 PM, Sven R. Kunze <srkunze@mail.de> wrote:
I've seen experienced coworkers rather adding superfluous pairs of parentheses just to make sure or because they still don't know better.
nothing wrong with superfluous parentheses -- it makes it clear and it's more robust in the face of refactoring. It fact, my answer to "precedence could be confusing here" is "use an extra parentheses and don't worry about it." -Chris -- Christopher Barker, Ph.D. Oceanographer Emergency Response Division NOAA/NOS/OR&R (206) 526-6959 voice 7600 Sand Point Way NE (206) 526-6329 fax Seattle, WA 98115 (206) 526-6317 main reception Chris.Barker@noaa.gov
![](https://secure.gravatar.com/avatar/7e41acaa8f6a0e0f5a7c645e93add55a.jpg?s=120&d=mm&r=g)
On Sep 28, 2015, at 09:02, Jeff Hardy <jdhardy@gmail.com> wrote:
It's worth reading the C# design team's notes and subsequent discussion on the associativity of "?." [1] since it goes around and around with no really good answer and no particularly intuitive behaviour.
Many of the problems raised there are irrelevant to Python: the fact that C# has "value types" that aren't referenced and can't be null, the fact that its ASTs are often processed by type-driven programming, the fact that it's not considered normal to raise and catch an exception in cases that aren't truly exceptional, the fact that there's (human-reader) ambiguity with ?:, the fact that . and [] are actually operators rather than a different kind of syntax, etc. There may be parallel problems to some of those issues in Python, but just assuming there will be because there are in C# doesn't establish that. The one argument that does carry over is that the "right-associative" version is harder to implement. That's worth discussing in Python terms, but it would be much more useful for someone to write an implementation to prove that the grammar, AST, and compiler code actually aren't that complicated, than to argue that they wouldn't necessarily be so. (The fact that it's harder to see where the exception comes from in something like spam(a?.b.c) is also the same in both languages, but that's already been discussed here, and I don't think that's a real problem in the first place--after all, a.b.c already raises an AttributeError that makes it just as hard to see whether it comes from None.b or None.c.)
![](https://secure.gravatar.com/avatar/b1f36e554be0e1ae19f9a74d6ece9107.jpg?s=120&d=mm&r=g)
On 09/28/2015 11:29 AM, Guido van Rossum wrote:
On Mon, Sep 28, 2015 at 9:02 AM, Jeff Hardy <jdhardy@gmail.com <mailto:jdhardy@gmail.com>> wrote:
-1 on the propagating member-access or index operators
Can someone explain with examples what this refers to?
"Member-access or index operators" refers to the proposed ?. or ?[ operators. "Propagating" refers to the proposed behavior where use of ?. or ?[ "propagates" through the following chain of operations. For example: x = foo?.bar.spam.eggs Where both `.spam` and `.eggs` would behave like `?.spam` and `?.eggs` (propagating None rather than raising AttributeError), simply because a `.?` had occurred earlier in the chain. So the above behaves differently from: temp = foo?.bar x = temp.spam.eggs Which raises questions about whether the propagation escapes parentheses, too: x = (foo?.bar).spam.eggs Carl
![](https://secure.gravatar.com/avatar/047f2332cde3730f1ed661eebb0c5686.jpg?s=120&d=mm&r=g)
On Mon, Sep 28, 2015 at 10:38 AM, Carl Meyer <carl@oddbird.net> wrote:
On 09/28/2015 11:29 AM, Guido van Rossum wrote:
On Mon, Sep 28, 2015 at 9:02 AM, Jeff Hardy <jdhardy@gmail.com <mailto:jdhardy@gmail.com>> wrote:
-1 on the propagating member-access or index operators
Can someone explain with examples what this refers to?
"Member-access or index operators" refers to the proposed ?. or ?[ operators.
Got that. :-)
"Propagating" refers to the proposed behavior where use of ?. or ?[ "propagates" through the following chain of operations. For example:
x = foo?.bar.spam.eggs
Where both `.spam` and `.eggs` would behave like `?.spam` and `?.eggs` (propagating None rather than raising AttributeError), simply because a `.?` had occurred earlier in the chain. So the above behaves differently from:
temp = foo?.bar x = temp.spam.eggs
Which raises questions about whether the propagation escapes parentheses, too:
x = (foo?.bar).spam.eggs
Oh, I see. That's evil. The correct behavior here is that "foo?.bar.spam.eggs" should mean the same as (None if foo is None else foo.bar.spam.eggs) (Stop until you understand that is *not* the same as either of the alternatives you describe.) I can see the confusion that led to the idea of "propagation" -- it probably comes from an attempt to define "foo?.bar" without reference to the context (in this case the relevant context is that it's followed by ".spam.eggs"). It should not escape parentheses. -- --Guido van Rossum (python.org/~guido)
![](https://secure.gravatar.com/avatar/b1f36e554be0e1ae19f9a74d6ece9107.jpg?s=120&d=mm&r=g)
On 09/28/2015 12:38 PM, Guido van Rossum wrote:
On Mon, Sep 28, 2015 at 10:38 AM, Carl Meyer <carl@oddbird.net
"Propagating" refers to the proposed behavior where use of ?. or ?[ "propagates" through the following chain of operations. For example:
x = foo?.bar.spam.eggs
Where both `.spam` and `.eggs` would behave like `?.spam` and `?.eggs` (propagating None rather than raising AttributeError), simply because a `.?` had occurred earlier in the chain. So the above behaves differently from:
temp = foo?.bar x = temp.spam.eggs
Which raises questions about whether the propagation escapes parentheses, too:
x = (foo?.bar).spam.eggs
Oh, I see. That's evil.
The correct behavior here is that "foo?.bar.spam.eggs" should mean the same as
(None if foo is None else foo.bar.spam.eggs)
(Stop until you understand that is *not* the same as either of the alternatives you describe.)
I see that. The distinction is "short-circuit" vs "propagate." Short-circuit is definitely more comprehensible and palatable. [snip]
It should not escape parentheses.
Good. I assume that the short-circuiting would follow the precedence order; that is, nothing with looser precedence than member and index access would be short-circuited. So, for example, foo?.bar['baz'].spam would short-circuit the indexing and the final member access, translating to foo.bar['baz'].spam if foo is not None else None but foo?.bar or 'baz' would mean (foo.bar if foo is not None else None) or 'baz' and would never evaluate to None. Similarly for any operator that binds less tightly than member/index access (which is basically all Python operators). AFAICS, under your proposed semantics what I said above is still true, that x = foo?.bar.baz would necessarily have a different meaning than temp = foo?.bar x = temp.baz Or put differently, that whereas these two are trivially equivalent (the definition of left-to-right binding within a precedence class): foo.bar.baz (foo.bar).baz these two are not equivalent: foo?.bar.baz (foo?.bar).baz I'm having trouble coming up with a parallel example where the existing short-circuit operators break "extractibility" of a sub-expression like that. I guess this is because the proposed short-circuiting still "breaks out of the precedence order" in a way that the existing short-circuiting operators don't. Both member access and indexing are within the same left-to-right binding precedence class, but the new operators would have a short-circuit effect that swallows operations beyond where normal left-to-right binding would suggest their effect should reach. Are there existing examples of behavior like this in Python that I'm missing? Carl
![](https://secure.gravatar.com/avatar/b1f36e554be0e1ae19f9a74d6ece9107.jpg?s=120&d=mm&r=g)
On 09/28/2015 01:43 PM, Carl Meyer wrote: [snip]
I assume that the short-circuiting would follow the precedence order; that is, nothing with looser precedence than member and index access would be short-circuited. So, for example,
foo?.bar['baz'].spam
would short-circuit the indexing and the final member access, translating to
foo.bar['baz'].spam if foo is not None else None
but
foo?.bar or 'baz'
would mean
(foo.bar if foo is not None else None) or 'baz'
and would never evaluate to None. Similarly for any operator that binds less tightly than member/index access (which is basically all Python operators).
For a possibly less-intuitive example of this principle (arbitrarily picking the operator that binds next-most-tightly), what should foo?.bar**3 mean? Carl
![](https://secure.gravatar.com/avatar/047f2332cde3730f1ed661eebb0c5686.jpg?s=120&d=mm&r=g)
On Mon, Sep 28, 2015 at 12:53 PM, Carl Meyer <carl@oddbird.net> wrote:
I assume that the short-circuiting would follow the precedence order; that is, nothing with looser precedence than member and index access would be short-circuited. So, for example,
foo?.bar['baz'].spam
would short-circuit the indexing and the final member access,
On 09/28/2015 01:43 PM, Carl Meyer wrote: [snip] translating to
foo.bar['baz'].spam if foo is not None else None
but
foo?.bar or 'baz'
would mean
(foo.bar if foo is not None else None) or 'baz'
and would never evaluate to None. Similarly for any operator that binds less tightly than member/index access (which is basically all Python operators).
For a possibly less-intuitive example of this principle (arbitrarily picking the operator that binds next-most-tightly), what should
foo?.bar**3
mean?
It's nonsense -- it means (foo?.bar)**3 but since foo?.bar can return None and None**3 is an error you shouldn't do that. But don't try to then come up with syntax that rejects foo?.bar**something statically, because something might be an object implements __rpow__. And I still don't see why this "principle" would be important. -- --Guido van Rossum (python.org/~guido)
![](https://secure.gravatar.com/avatar/b1f36e554be0e1ae19f9a74d6ece9107.jpg?s=120&d=mm&r=g)
On 09/28/2015 01:57 PM, Guido van Rossum wrote:
On Mon, Sep 28, 2015 at 12:53 PM, Carl Meyer <carl@oddbird.net <mailto:carl@oddbird.net>> wrote:
On 09/28/2015 01:43 PM, Carl Meyer wrote: [snip] > I assume that the short-circuiting would follow the precedence > order; that is, nothing with looser precedence than member and index > access would be short-circuited. So, for example, > > foo?.bar['baz'].spam > > would short-circuit the indexing and the final member access, translating to > > foo.bar['baz'].spam if foo is not None else None > > but > > foo?.bar or 'baz' > > would mean > > (foo.bar if foo is not None else None) or 'baz' > > and would never evaluate to None. Similarly for any operator that binds > less tightly than member/index access (which is basically all Python > operators).
For a possibly less-intuitive example of this principle (arbitrarily picking the operator that binds next-most-tightly), what should
foo?.bar**3
mean?
It's nonsense -- it means (foo?.bar)**3 but since foo?.bar can return None and None**3 is an error you shouldn't do that. But don't try to then come up with syntax that rejects foo?.bar**something statically, because something might be an object implements __rpow__.
And I still don't see why this "principle" would be important.
The only "principle" in question here is "nothing with looser precedence than member and index access would be short-circuited," and you seem to agree with it. I was just making sure that foo?.bar**3 couldn't possibly mean (foo.bar**3 if foo is None else None) and I'm glad it couldn't. Carl
![](https://secure.gravatar.com/avatar/047f2332cde3730f1ed661eebb0c5686.jpg?s=120&d=mm&r=g)
On Mon, Sep 28, 2015 at 12:43 PM, Carl Meyer <carl@oddbird.net> wrote:
On 09/28/2015 12:38 PM, Guido van Rossum wrote:
On Mon, Sep 28, 2015 at 10:38 AM, Carl Meyer <carl@oddbird.net
"Propagating" refers to the proposed behavior where use of ?. or ?[ "propagates" through the following chain of operations. For example:
x = foo?.bar.spam.eggs
Where both `.spam` and `.eggs` would behave like `?.spam` and
`?.eggs`
(propagating None rather than raising AttributeError), simply
because a
`.?` had occurred earlier in the chain. So the above behaves
differently
from:
temp = foo?.bar x = temp.spam.eggs
Which raises questions about whether the propagation escapes parentheses, too:
x = (foo?.bar).spam.eggs
Oh, I see. That's evil.
The correct behavior here is that "foo?.bar.spam.eggs" should mean the same as
(None if foo is None else foo.bar.spam.eggs)
(Stop until you understand that is *not* the same as either of the alternatives you describe.)
I see that. The distinction is "short-circuit" vs "propagate." Short-circuit is definitely more comprehensible and palatable.
Right.
[snip]
It should not escape parentheses.
Good. I assume that the short-circuiting would follow the precedence order; that is, nothing with looser precedence than member and index access would be short-circuited. So, for example,
foo?.bar['baz'].spam
would short-circuit the indexing and the final member access, translating to
foo.bar['baz'].spam if foo is not None else None
but
foo?.bar or 'baz'
would mean
(foo.bar if foo is not None else None) or 'baz'
and would never evaluate to None. Similarly for any operator that binds less tightly than member/index access (which is basically all Python operators).
Correct. The scope of ? would be all following .foo, .[stuff], or .(args) -- but stopping at any other operator (including parens).
AFAICS, under your proposed semantics what I said above is still true, that
x = foo?.bar.baz
would necessarily have a different meaning than
temp = foo?.bar x = temp.baz
Or put differently, that whereas these two are trivially equivalent (the definition of left-to-right binding within a precedence class):
foo.bar.baz (foo.bar).baz
these two are not equivalent:
foo?.bar.baz (foo?.bar).baz
Right.
I'm having trouble coming up with a parallel example where the existing short-circuit operators break "extractibility" of a sub-expression like that.
Why is that an interesting property?
I guess this is because the proposed short-circuiting still "breaks out of the precedence order" in a way that the existing short-circuiting operators don't. Both member access and indexing are within the same left-to-right binding precedence class, but the new operators would have a short-circuit effect that swallows operations beyond where normal left-to-right binding would suggest their effect should reach.
Are there existing examples of behavior like this in Python that I'm missing?
I don't know, but I think you shouldn't worry about this. -- --Guido van Rossum (python.org/~guido)
![](https://secure.gravatar.com/avatar/b1f36e554be0e1ae19f9a74d6ece9107.jpg?s=120&d=mm&r=g)
On 09/28/2015 01:53 PM, Guido van Rossum wrote:
On Mon, Sep 28, 2015 at 12:43 PM, Carl Meyer <carl@oddbird.net Or put differently, that whereas these two are trivially equivalent (the definition of left-to-right binding within a precedence class):
foo.bar.baz (foo.bar).baz
these two are not equivalent:
foo?.bar.baz (foo?.bar).baz
Right.
I'm having trouble coming up with a parallel example where the existing short-circuit operators break "extractibility" of a sub-expression like that.
Why is that an interesting property?
Because breaking up an overly-complex expression into smaller expressions by means of extracting sub-expressions into temporary variables is a common programming task (in my experience anyway -- especially when trying to decipher some long-gone programmer's overly-complex code), and it's usually one that can be handled pretty mechanically according to precedence rules, without having to consider that some operators might have action-at-a-distance beyond their precedence.
I guess this is because the proposed short-circuiting still "breaks out of the precedence order" in a way that the existing short-circuiting operators don't. Both member access and indexing are within the same left-to-right binding precedence class, but the new operators would have a short-circuit effect that swallows operations beyond where normal left-to-right binding would suggest their effect should reach.
Are there existing examples of behavior like this in Python that I'm missing?
I don't know, but I think you shouldn't worry about this.
I think it's kind of odd, but if nobody else is worried about it, I won't worry about it either :-) Carl
![](https://secure.gravatar.com/avatar/047f2332cde3730f1ed661eebb0c5686.jpg?s=120&d=mm&r=g)
On Mon, Sep 28, 2015 at 1:00 PM, Carl Meyer <carl@oddbird.net> wrote:
On Mon, Sep 28, 2015 at 12:43 PM, Carl Meyer <carl@oddbird.net Or put differently, that whereas these two are trivially equivalent (the definition of left-to-right binding within a precedence class):
foo.bar.baz (foo.bar).baz
these two are not equivalent:
foo?.bar.baz (foo?.bar).baz
Right.
I'm having trouble coming up with a parallel example where the existing short-circuit operators break "extractibility" of a sub-expression
On 09/28/2015 01:53 PM, Guido van Rossum wrote: like
that.
Why is that an interesting property?
Because breaking up an overly-complex expression into smaller expressions by means of extracting sub-expressions into temporary variables is a common programming task (in my experience anyway -- especially when trying to decipher some long-gone programmer's overly-complex code), and it's usually one that can be handled pretty mechanically according to precedence rules, without having to consider that some operators might have action-at-a-distance beyond their precedence.
Well, if just the foo?.bar.baz part is already too complex you probably need to reconsider your career. :-) Seriously, when breaking things into smaller parts you *have* to understand the shortcut properties. You can't break "foo() or bar()" into a = foo() b = bar() return a or b either.
I guess this is because the proposed short-circuiting still "breaks
out
of the precedence order" in a way that the existing short-circuiting operators don't. Both member access and indexing are within the same left-to-right binding precedence class, but the new operators would
have
a short-circuit effect that swallows operations beyond where normal left-to-right binding would suggest their effect should reach.
Are there existing examples of behavior like this in Python that I'm missing?
I don't know, but I think you shouldn't worry about this.
I think it's kind of odd, but if nobody else is worried about it, I won't worry about it either :-)
Good idea. -- --Guido van Rossum (python.org/~guido)
![](https://secure.gravatar.com/avatar/5ce43469c0402a7db8d0cf86fa49da5a.jpg?s=120&d=mm&r=g)
On 2015-09-28 21:06, Guido van Rossum wrote:
On Mon, Sep 28, 2015 at 1:00 PM, Carl Meyer <carl@oddbird.net <mailto:carl@oddbird.net>> wrote:
On 09/28/2015 01:53 PM, Guido van Rossum wrote: > On Mon, Sep 28, 2015 at 12:43 PM, Carl Meyer <carl@oddbird.net <mailto:carl@oddbird.net> > Or put differently, that whereas these two are trivially equivalent (the > definition of left-to-right binding within a precedence class): > > foo.bar.baz > (foo.bar).baz > > these two are not equivalent: > > foo?.bar.baz > (foo?.bar).baz > > > Right. > > > I'm having trouble coming up with a parallel example where the existing > short-circuit operators break "extractibility" of a sub-expression like > that. > > > Why is that an interesting property?
Because breaking up an overly-complex expression into smaller expressions by means of extracting sub-expressions into temporary variables is a common programming task (in my experience anyway -- especially when trying to decipher some long-gone programmer's overly-complex code), and it's usually one that can be handled pretty mechanically according to precedence rules, without having to consider that some operators might have action-at-a-distance beyond their precedence.
Well, if just the foo?.bar.baz part is already too complex you probably need to reconsider your career. :-)
Seriously, when breaking things into smaller parts you *have* to understand the shortcut properties. You can't break "foo() or bar()" into
a = foo() b = bar() return a or b
either.
Exactly. Can you break: result = do_this() if test() else do_that() into parts without changing its meaning/behaviour? condition = test() true_result = do_this() false_result = do_that() result = true_result if condition else false_result The ? 'operators' are syntactic sugar.
> I guess this is because the proposed short-circuiting still "breaks out > of the precedence order" in a way that the existing short-circuiting > operators don't. Both member access and indexing are within the same > left-to-right binding precedence class, but the new operators would have > a short-circuit effect that swallows operations beyond where normal > left-to-right binding would suggest their effect should reach. > > Are there existing examples of behavior like this in Python that I'm > missing? > > > I don't know, but I think you shouldn't worry about this.
I think it's kind of odd, but if nobody else is worried about it, I won't worry about it either :-)
Good idea.
![](https://secure.gravatar.com/avatar/ebf132362b622423ed5baca2988911b8.jpg?s=120&d=mm&r=g)
The ? Modifying additional attribute accesses beyond just the immediate one bothers me too and feels more ruby than python to me. Sent from my iPhone
On Sep 28, 2015, at 4:00 PM, Carl Meyer <carl@oddbird.net> wrote:
I think it's kind of odd, but if nobody else is worried about it, I won't worry about it either :-)
![](https://secure.gravatar.com/avatar/047f2332cde3730f1ed661eebb0c5686.jpg?s=120&d=mm&r=g)
On Mon, Sep 28, 2015 at 1:15 PM, Donald Stufft <donald@stufft.io> wrote:
The ? Modifying additional attribute accesses beyond just the immediate one bothers me too and feels more ruby than python to me.
Really? Have you thought about it? Suppose I have an object post which may be None or something with a tag attribute which should be a string. And suppose I want to get the lowercased tag, if the object exists, else None. This seems a perfect use case for writing post?.tag.lower() -- this signifies that post may be None but if it exists, post.tag is not expected to be None. So basically I want the equivalent of (post.tag.lower() if post is not None else None). But if post?.tag.lower() were interpreted strictly as (post?.tag).lower(), then I would have to write post?.tag?.lower?(), which is an abomination. OTOH if post?.tag.lower() automatically meant post?.tag?.lower?() then I would silently get no error when post exists but post.tag is None (which in this example is an error). -- --Guido van Rossum (python.org/~guido)
![](https://secure.gravatar.com/avatar/e572da4355c07804e3300bf879ffbe64.jpg?s=120&d=mm&r=g)
Guido van Rossum <guido@python.org> writes:
This seems a perfect use case for writing post?.tag.lower() -- this signifies that post may be None but if it exists, post.tag is not expected to be None. So basically I want the equivalent of (post.tag.lower() if post is not None else None).
You're deliberately choosing straightforward examples. That's fine for showing the intended use case, but it does mean dismissing the concerns about ambiguity in complex cases. It also means the use cases are so simply they are easily expressed succinctly with existing syntax, with the advantage of being more explicit in their effect; so they don't argue strongly for the need to add the new syntax. So, the corner case examples in this thread, which mix up precedence, are useful because they show how confusion is increased by making the precedence and binding rules more complicated. -- \ “People are very open-minded about new things, as long as | `\ they're exactly like the old ones.” —Charles F. Kettering | _o__) | Ben Finney
![](https://secure.gravatar.com/avatar/047f2332cde3730f1ed661eebb0c5686.jpg?s=120&d=mm&r=g)
On Mon, Sep 28, 2015 at 1:38 PM, Ben Finney <ben+python@benfinney.id.au> wrote:
Guido van Rossum <guido@python.org> writes:
This seems a perfect use case for writing post?.tag.lower() -- this signifies that post may be None but if it exists, post.tag is not expected to be None. So basically I want the equivalent of (post.tag.lower() if post is not None else None).
You're deliberately choosing straightforward examples. That's fine for showing the intended use case, but it does mean dismissing the concerns about ambiguity in complex cases.
It also means the use cases are so simply they are easily expressed succinctly with existing syntax, with the advantage of being more explicit in their effect; so they don't argue strongly for the need to add the new syntax.
So, the corner case examples in this thread, which mix up precedence, are useful because they show how confusion is increased by making the precedence and binding rules more complicated.
But your argument seems to boil down to "it is possible to write obfuscated code using this feature". If you want to dumb down the feature so that foo?.bar.baz means just (foo?.bar).baz then it's useless and I should just reject the PEP. -- --Guido van Rossum (python.org/~guido)
![](https://secure.gravatar.com/avatar/b1f36e554be0e1ae19f9a74d6ece9107.jpg?s=120&d=mm&r=g)
On 09/28/2015 02:54 PM, Guido van Rossum wrote:
If you want to dumb down the feature so that foo?.bar.baz means just (foo?.bar).baz then it's useless and I should just reject the PEP.
I think you're right that in practice ?. and ?[ would probably be just fine, because the scope of their action is still quite limited. But even if they are rejected, I think a simple `??` or `or?` (or however it's spelled) operator to reduce the repetition of "x if x is not None else y" is worth consideration on its own merits. This operator is entirely unambiguous, and I think would be useful and frequently used, whether or not ?. and ?[ are added along with it. Carl
![](https://secure.gravatar.com/avatar/01aa7d6d4db83982a2f6dd363d0ee0f3.jpg?s=120&d=mm&r=g)
On Sep 28, 2015, at 03:04 PM, Carl Meyer wrote:
But even if they are rejected, I think a simple `??` or `or?` (or however it's spelled) operator to reduce the repetition of "x if x is not None else y" is worth consideration on its own merits. This operator is entirely unambiguous, and I think would be useful and frequently used, whether or not ?. and ?[ are added along with it.
But why is it an improvement? The ternary operator is entirely obvious and readable, and at least in my experience, is rare enough that the repetition doesn't hurt my fingers that much. It seems like such a radical, ugly new syntax unjustified by the frequency of use and readability improvement. Cheers, -Barry
![](https://secure.gravatar.com/avatar/b1f36e554be0e1ae19f9a74d6ece9107.jpg?s=120&d=mm&r=g)
Hi Barry, On 09/29/2015 11:35 AM, Barry Warsaw wrote:
On Sep 28, 2015, at 03:04 PM, Carl Meyer wrote:
But even if they are rejected, I think a simple `??` or `or?` (or however it's spelled) operator to reduce the repetition of "x if x is not None else y" is worth consideration on its own merits. This operator is entirely unambiguous, and I think would be useful and frequently used, whether or not ?. and ?[ are added along with it.
But why is it an improvement? The ternary operator is entirely obvious and readable, and at least in my experience, is rare enough that the repetition doesn't hurt my fingers that much. It seems like such a radical, ugly new syntax unjustified by the frequency of use and readability improvement.
I find the repetition irritating enough that I'm tempted to use 'or' instead, even when I know it's not technically the semantics I want. (In most cases, the difference probably doesn't matter, and when it actually does, I probably know that and write out the full ternary.) And I find plenty of other code using `or` when it ought to be using a ternary with `is None` (but again, most of the time in practice it's fine.) Most of this code is in defaults-handling; there've been plenty of examples in the thread. I find the explicit if-block painful if there's more than one argument with a None-default to be handled; YMMV. I agree that I don't love any of the syntax suggestions so far, and without a less ugly syntax, it's probably dead. If it was in the language, I'd use it, but I don't feel strongly about it. Carl
![](https://secure.gravatar.com/avatar/ad24799da38da64c3b51b745087d60fd.jpg?s=120&d=mm&r=g)
On Tue, Sep 29, 2015 at 10:35 AM, Barry Warsaw <barry@python.org> wrote:
On Sep 28, 2015, at 03:04 PM, Carl Meyer wrote:
But even if they are rejected, I think a simple `??` or `or?` (or however it's spelled) operator to reduce the repetition of "x if x is not None else y" is worth consideration on its own merits. This operator is entirely unambiguous, and I think would be useful and frequently used, whether or not ?. and ?[ are added along with it.
But why is it an improvement? The ternary operator is entirely obvious and readable, and at least in my experience, is rare enough that the repetition doesn't hurt my fingers that much. It seems like such a radical, ugly new syntax unjustified by the frequency of use and readability improvement.
I use it all over the place in C# code, where it makes null checks much cleaner, and the punctuation choice makes sense: var x = foo != null ? foo : ""; var y = foo ?? ""; (it also has other uses in C# relating to nullable types that aren't relevant in Python.) I'd argue the same is true in Python, if a decent way to spell it can be found: x = foo if foo is not None else "" y = foo or? "" It's pure syntactic sugar, but it *is* pretty sweet. (It would also make get-with-default unnecessary, but since it already exists that's not a useful argument.) - Jeff
![](https://secure.gravatar.com/avatar/4655830e7145e4fbe09aca7a38589721.jpg?s=120&d=mm&r=g)
On 9/29/2015 1:43 PM, Jeff Hardy wrote:
I'd argue the same is true in Python, if a decent way to spell it can be found:
x = foo if foo is not None else "" y = foo or? ""
It's pure syntactic sugar, but it *is* pretty sweet.
as to or? variants -- I'd rather see nor: x = foo nor 'foo was None' Just to add my two cents worth. Emile
![](https://secure.gravatar.com/avatar/7e41acaa8f6a0e0f5a7c645e93add55a.jpg?s=120&d=mm&r=g)
LOn Sep 29, 2015, at 16:27, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Emile van Sebille wrote:
x = foo nor 'foo was None'
Cute, but unfortunately it conflicts with established usage of the word 'nor', which would suggest that a nor b == not (a or b).
Agreed. If this is going to be a keyword rather than a symbol, it really has to read like English, or at least like abbreviated English, with the right meaning--something like "foo, falling back to 'foo was None' if needed". Something that reads like English with a completely different meaning is a bad idea.
![](https://secure.gravatar.com/avatar/25ef0a6698317c91220d6a1a89543df3.jpg?s=120&d=mm&r=g)
On Tue, Sep 29, 2015 at 7:57 PM, Andrew Barnert via Python-ideas < python-ideas@python.org> wrote:
If this is going to be a keyword rather than a symbol, it really has to read like English, or at least like abbreviated English,
I am -1 on this whole idea, but the keyword that comes to mind is "def": x def [] may be read as x DEFaulting to []. If this was a Python 4 idea, I would suggest repurposing the rarely used xor operator: ^ and make x ^ y return the non-None of x and y or None if both are None.
![](https://secure.gravatar.com/avatar/25ef0a6698317c91220d6a1a89543df3.jpg?s=120&d=mm&r=g)
On Tue, Sep 29, 2015 at 8:24 PM, Alexander Belopolsky < alexander.belopolsky@gmail.com> wrote:
... suggest repurposing the rarely used xor operator: ^ and make x ^ y return the non-None of x and y or None if both are None.
.. and x if both are not None to allow x ^= y to work as expected.
![](https://secure.gravatar.com/avatar/bdfe4d3b80b38bcdd9dc21b191022ef2.jpg?s=120&d=mm&r=g)
On Wed, Sep 30, 2015 at 1:24 AM, Alexander Belopolsky < alexander.belopolsky@gmail.com> wrote:
If this was a Python 4 idea, I would suggest repurposing the rarely used xor operator: ^ and make x ^ y return the non-None of x and y or None if both are None.
I find ^ quite useful for sets, and would rather not see it repurposed in this way. Another possibility: There could be a binary version of the tilde operator, which is currently only unary: x ~ y to mean "x if x is not None else y". But I am also -1 on the whole idea, as I rarely encounter situations that would benefit from this construct. Nathan
![](https://secure.gravatar.com/avatar/ad24799da38da64c3b51b745087d60fd.jpg?s=120&d=mm&r=g)
On Tue, Sep 29, 2015 at 5:24 PM, Alexander Belopolsky < alexander.belopolsky@gmail.com> wrote:
On Tue, Sep 29, 2015 at 7:57 PM, Andrew Barnert via Python-ideas < python-ideas@python.org> wrote:
If this is going to be a keyword rather than a symbol, it really has to read like English, or at least like abbreviated English,
I am -1 on this whole idea, but the keyword that comes to mind is "def":
x def []
may be read as x DEFaulting to [].
'def' is currently short for 'define', which would be too confusing. Spelling out 'default' isn't so bad, though: self.x = x default [] And if it's going to be that long anyway, we might as well just put a `default` function in the builtins: self.x = default(x, []) I actually really like 'otherwise', but it's certainly not brief: self.x = x if x is not None else [] self.x = x otherwise [] That said, it's not used *that* often either, so maybe it's acceptable. - Jeff
![](https://secure.gravatar.com/avatar/d67ab5d94c2fed8ab6b727b62dc1b213.jpg?s=120&d=mm&r=g)
On Thu, Oct 1, 2015 at 2:41 AM, Jeff Hardy <jdhardy@gmail.com> wrote:
'def' is currently short for 'define', which would be too confusing. Spelling out 'default' isn't so bad, though:
self.x = x default []
And if it's going to be that long anyway, we might as well just put a `default` function in the builtins:
self.x = default(x, [])
I'd prefer it to have language support rather than a builtin, so it can shortcircuit. It won't often be important, but it would be nice to be able to put a function call in there or something. ChrisA
![](https://secure.gravatar.com/avatar/2828041405aa313004b6549acf918228.jpg?s=120&d=mm&r=g)
On 09/30/2015 12:41 PM, Jeff Hardy wrote:
'def' is currently short for 'define', which would be too confusing. Spelling out 'default' isn't so bad, though:
self.x = x default []
And if it's going to be that long anyway, we might as well just put a `default` function in the builtins:
self.x = default(x, [])
You lose the short circuiting.
I actually really like 'otherwise', but it's certainly not brief:
self.x = x if x is not None else [] self.x = x otherwise []
I'm -1 on needing syntax for this, but if we're going to do it, this is my favorite version I've seen so far. The usual caveats about adding a keyword apply. Eric.
![](https://secure.gravatar.com/avatar/a07907d1d5962d947d1a414362d4331c.jpg?s=120&d=mm&r=g)
On Wed, Sep 30, 2015 at 12:49 PM, Eric V. Smith <eric@trueblade.com> wrote:
self.x = x if x is not None else [] self.x = x otherwise []
I'm -1 on needing syntax for this, but if we're going to do it, this is my favorite version I've seen so far. The usual caveats about adding a keyword apply.
Also feel this is the most intuitive... every other syntax seems really hard to read, however, the caveat I am thinking here is synonym of else and otherwise. We really should go any more complex. Those ? and null with/without exception seems awfully complex to reason. I'd spell out 10 lines if I had to. If fact, is it bad if we make else working for such brevity? BTW, this syntax just defeats the example in the PEP: [PEP 0505 - https://www.python.org/dev/peps/pep-0505/] This particular formulation has the undesirable effect of putting the
operands in an unintuitive order: the brain thinks, "use data if possible and use [] as a fallback," but the code puts the fallback * before * the preferred value.
John
![](https://secure.gravatar.com/avatar/7e41acaa8f6a0e0f5a7c645e93add55a.jpg?s=120&d=mm&r=g)
On Sep 30, 2015, at 13:58, Piotr Duda <duda.piotr@gmail.com> wrote:
What about something like: z = x if is not None else []
For something as simple as "x", this doesn't seem much better than what we can already do: z = x if x is not None else [] For a complex expression that might be incorrect/expensive/dangerous to call multiple times, it might be useful, but I think it would read a lot better with an explicit pronoun: z = dangerous_thing(arg) if it is not None else [] In natural languages, "it" is already complex enough; adding in subject elision makes parsing even harder. I think the same would be true here. Also, explicit "it" would be usable in other situations: z = dangerous_thing(arg) if it.value() > 3 else DummyValue(3) And it gives you something to look up in the docs: help(it) can tell me how to figure out what "it" refers to, but how would I find that out with your version? Anyway, I still don't like it even with the explicit pronoun, but maybe that's just AppleScript flashbacks. :)
![](https://secure.gravatar.com/avatar/7256049d410aa28c240527e1a779799e.jpg?s=120&d=mm&r=g)
On Sep 30 2015, Andrew Barnert via Python-ideas <python-ideas-+ZN9ApsXKcEdnm+yROfE0A@public.gmane.org> wrote:
Also, explicit "it" would be usable in other situations:
z = dangerous_thing(arg) if it.value() > 3 else DummyValue(3)
Really? I'm not sure if "it" is "dangerous_thing" or "dangerous_thing(arg)". Best, -Nikolaus -- GPG encrypted emails preferred. Key id: 0xD113FCAC3C4E599F Fingerprint: ED31 791B 2C5C 1613 AF38 8B8A D113 FCAC 3C4E 599F »Time flies like an arrow, fruit flies like a Banana.«
![](https://secure.gravatar.com/avatar/3dd475b8aaa5292d74cb0c3f76c3f196.jpg?s=120&d=mm&r=g)
On Wed, Sep 30, 2015, at 18:42, Nikolaus Rath wrote:
On Sep 30 2015, Andrew Barnert via Python-ideas <python-ideas-+ZN9ApsXKcEdnm+yROfE0A@public.gmane.org> wrote:
Also, explicit "it" would be usable in other situations:
z = dangerous_thing(arg) if it.value() > 3 else DummyValue(3)
Really? I'm not sure if "it" is "dangerous_thing" or "dangerous_thing(arg)".
I thought it was arg, though it's a bit silly for it to be. Why not go full Scheme and add a way to do lexical expressions that capture values for multiple use: z = (let it=arg: dangerous_thing(it) if it.value() > 3 else DummyValue(3)) Would be equivalent to z = (lambda it: dangerous_thing(it) if it.value() > 3 else DummyValue(3))(arg) Only without necessarily actually instantiating a lambda (i.e. it could push the result of arg [which may be any expression] onto the stack or in an anonymous or obscurely-named local variable slot to refer whenever it is referenced in the inner expression.
![](https://secure.gravatar.com/avatar/7e41acaa8f6a0e0f5a7c645e93add55a.jpg?s=120&d=mm&r=g)
On Sep 30, 2015, at 09:41, Jeff Hardy <jdhardy@gmail.com> wrote:
I actually really like 'otherwise', but it's certainly not brief:
self.x = x if x is not None else [] self.x = x otherwise []
That said, it's not used *that* often either, so maybe it's acceptable.
The big problem with "otherwise", "orelse", or anything else that's a synonym of "or" is that it's a synonym of "or". There is nothing to tell the novice, or the sometime Python user who's just come back from 3 months with Ruby or Objective C or whatever, which one means a falsey check and which one means a None check. They both read the same in English, and the difference would be unique to Python rather than a general programming thing. So you'd end up with hundreds of articles and blog posts explaining, often poorly, the difference and when to use each--just as you see for == vs. ===, eq vs. eql, etc. in other languages.
![](https://secure.gravatar.com/avatar/5615a372d9866f203a22b2c437527bbb.jpg?s=120&d=mm&r=g)
On Wed, Sep 30, 2015 at 09:41:53AM -0700, Jeff Hardy wrote:
'def' is currently short for 'define', which would be too confusing. Spelling out 'default' isn't so bad, though:
self.x = x default []
Heh, people jest :-) Oh, you're serious? That just goes to show how different we all are. I think that using "default" as an infix operator is hideous. "default" as a function/verb? Sure, that works fine: if len(missed_payments) > 5: loan.default() "default" as a variable/noun? Absolutely fine: default = "something or other" But "x default z" just doesn't read well: in English, it sounds like you are ordering x to cause z to default. Not very many words read naturally as a binary infix operator. Besides, I would expect that making "default" a keyword will break a *huge* number of programs. I'm sure that many, many people use it as a variable or parameter name.
And if it's going to be that long anyway, we might as well just put a `default` function in the builtins:
self.x = default(x, [])
That's useless, as it breaks the short-circuiting property, which is one of the critical motives for introducing the null coalescing operator. Without the short-circuiting property, there's no point in making it a built-in. If all you want is a default function, then just add it as a helper function to the top of your code: def default(obj, alternative): if obj is None: return alternative return obj
I actually really like 'otherwise', but it's certainly not brief:
self.x = x if x is not None else [] self.x = x otherwise []
That's almost half the length of the alternative. And if you have something more realistic, you save proportionally even less: document if document is not None else Document() document otherwise Document() But personally, I have absolutely zero interest in the null coalescing operator on its own. I don't object to it specifically, but what really interests me are the two (pseudo)operators, null-aware indexing and null-aware attribute lookup. How do you extend "otherwise" to work with the [] and . operators? -- Steve
![](https://secure.gravatar.com/avatar/db5b03704c129196a4e9415e55413ce6.jpg?s=120&d=mm&r=g)
What about 'otherwise'? x = a otherwise b On September 29, 2015 6:57:39 PM CDT, Andrew Barnert via Python-ideas <python-ideas@python.org> wrote:
LOn Sep 29, 2015, at 16:27, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Emile van Sebille wrote:
x = foo nor 'foo was None'
Cute, but unfortunately it conflicts with established usage of the word 'nor', which would suggest that a nor b == not (a or b).
Agreed. If this is going to be a keyword rather than a symbol, it really has to read like English, or at least like abbreviated English, with the right meaning--something like "foo, falling back to 'foo was None' if needed". Something that reads like English with a completely different meaning is a bad idea.
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
-- Sent from my Nexus 5 with K-9 Mail. Please excuse my brevity. CURRENTLY LISTENING TO: Vermilion Fire (Final Fantasy Type-0)
![](https://secure.gravatar.com/avatar/880ac02012dcaf99f0392f69af4f8597.jpg?s=120&d=mm&r=g)
Or: x = a orelse b # Visual Basic has a short-circuiting OrElse operator for boolean operands x = a orifNone b On 30/09/2015 01:39, Ryan Gonzalez wrote:
What about 'otherwise'?
x = a otherwise b
On September 29, 2015 6:57:39 PM CDT, Andrew Barnert via Python-ideas <python-ideas@python.org> wrote:
LOn Sep 29, 2015, at 16:27, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Emile van Sebille wrote:
x = foo nor 'foo was None'
Cute, but unfortunately it conflicts with established usage of the word 'nor', which would suggest that a nor b == not (a or b).
Agreed. If this is going to be a keyword rather than a symbol, it really has to read like English, or at least like abbreviated English, with the right meaning--something like "foo, falling back to 'foo was None' if needed". Something that reads like English with a completely different meaning is a bad idea.
------------------------------------------------------------------------
Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct:http://python.org/psf/codeofconduct/
-- Sent from my Nexus 5 with K-9 Mail. Please excuse my brevity. CURRENTLY LISTENING TO: Vermilion Fire (Final Fantasy Type-0)
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
![](https://secure.gravatar.com/avatar/130fe9f08ce5d2b1716d32438a58c867.jpg?s=120&d=mm&r=g)
On 30.09.2015 13:00, Rob Cliffe wrote:
Or: x = a orelse b # Visual Basic has a short-circuiting OrElse operator for boolean operands x = a orifNone b
On 30/09/2015 01:39, Ryan Gonzalez wrote:
What about 'otherwise'?
x = a otherwise b
The only reason why I would prefer "else" over the proposed alternatives: it's already a reserved keyword and it's not really necessary to waste another one. Otherwise, I don't care too much. Best, Sven
![](https://secure.gravatar.com/avatar/880ac02012dcaf99f0392f69af4f8597.jpg?s=120&d=mm&r=g)
On 30/09/2015 23:57, Sven R. Kunze wrote:
On 30.09.2015 13:00, Rob Cliffe wrote:
Or: x = a orelse b # Visual Basic has a short-circuiting OrElse operator for boolean operands x = a orifNone b
On 30/09/2015 01:39, Ryan Gonzalez wrote:
What about 'otherwise'?
x = a otherwise b
The only reason why I would prefer "else" over the proposed alternatives: it's already a reserved keyword and it's not really necessary to waste another one. Otherwise, I don't care too much.
Best, Sven Are you suggesting that "a else b" is a possibility? That's a no-no because a if b else c else d is ambiguous; it could mean either of a if b else (c else d) (a if b else c) else d Rob
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
![](https://secure.gravatar.com/avatar/db5b03704c129196a4e9415e55413ce6.jpg?s=120&d=mm&r=g)
On September 30, 2015 9:08:47 PM CDT, Rob Cliffe <rob.cliffe@btinternet.com> wrote:
On 30/09/2015 23:57, Sven R. Kunze wrote:
On 30.09.2015 13:00, Rob Cliffe wrote:
Or: x = a orelse b # Visual Basic has a short-circuiting OrElse operator for boolean operands x = a orifNone b
On 30/09/2015 01:39, Ryan Gonzalez wrote:
What about 'otherwise'?
x = a otherwise b
The only reason why I would prefer "else" over the proposed alternatives: it's already a reserved keyword and it's not really necessary to waste another one. Otherwise, I don't care too much.
Best, Sven Are you suggesting that "a else b" is a possibility? That's a no-no because a if b else c else d is ambiguous; it could mean either of a if b else (c else d) (a if b else c) else d Rob
I already said that...
_______________________________________________ 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/
-- Sent from my Nexus 5 with K-9 Mail. Please excuse my brevity.
![](https://secure.gravatar.com/avatar/130fe9f08ce5d2b1716d32438a58c867.jpg?s=120&d=mm&r=g)
On 01.10.2015 04:30, Ryan Gonzalez wrote:
On September 30, 2015 9:08:47 PM CDT, Rob Cliffe <rob.cliffe@btinternet.com> wrote:
On 30/09/2015 23:57, Sven R. Kunze wrote:
The only reason why I would prefer "else" over the proposed alternatives: it's already a reserved keyword and it's not really necessary to waste another one. Otherwise, I don't care too much.
Best, Sven
Are you suggesting that "a else b" is a possibility? That's a no-no because a if b else c else d is ambiguous; it could mean either of a if b else (c else d) (a if b else c) else d Rob
I already said that...
And others already said "use parentheses" if in doubt.
![](https://secure.gravatar.com/avatar/7256049d410aa28c240527e1a779799e.jpg?s=120&d=mm&r=g)
On Sep 30 2015, Greg Ewing <greg.ewing-F+z8Qja7x9Xokq/tPzqvJg@public.gmane.org> wrote:
Emile van Sebille wrote:
x = foo nor 'foo was None'
Cute, but unfortunately it conflicts with established usage of the word 'nor', which would suggest that a nor b == not (a or b).
The idea of using a named operator instead of some symbol has merit though. What about "a orn b" or "a orin b" (*or* *i*f *n*one)? The latter might be especially appealing to Tolkien readers :-). Best, -Nikolaus -- GPG encrypted emails preferred. Key id: 0xD113FCAC3C4E599F Fingerprint: ED31 791B 2C5C 1613 AF38 8B8A D113 FCAC 3C4E 599F »Time flies like an arrow, fruit flies like a Banana.«
![](https://secure.gravatar.com/avatar/5ce43469c0402a7db8d0cf86fa49da5a.jpg?s=120&d=mm&r=g)
On 2015-09-29 21:43, Jeff Hardy wrote:
On Tue, Sep 29, 2015 at 10:35 AM, Barry Warsaw <barry@python.org <mailto:barry@python.org>> wrote:
On Sep 28, 2015, at 03:04 PM, Carl Meyer wrote:
>But even if they are rejected, I think a simple `??` or `or?` (or >however it's spelled) operator to reduce the repetition of "x if x is >not None else y" is worth consideration on its own merits. This operator >is entirely unambiguous, and I think would be useful and frequently >used, whether or not ?. and ?[ are added along with it.
But why is it an improvement? The ternary operator is entirely obvious and readable, and at least in my experience, is rare enough that the repetition doesn't hurt my fingers that much. It seems like such a radical, ugly new syntax unjustified by the frequency of use and readability improvement.
I use it all over the place in C# code, where it makes null checks much cleaner, and the punctuation choice makes sense:
var x = foo != null ? foo : ""; var y = foo ?? "";
(it also has other uses in C# relating to nullable types that aren't relevant in Python.)
I'd argue the same is true in Python, if a decent way to spell it can be found:
x = foo if foo is not None else "" y = foo or? ""
It's pure syntactic sugar, but it *is* pretty sweet.
(It would also make get-with-default unnecessary, but since it already exists that's not a useful argument.)
It's only just occurred to me that there's a small inconsistency here. The "?.", "?[" and "?(" will short-circuit on None, whereas the "??" will short-circuit on non-None. Would that cause any confusion in practice?
![](https://secure.gravatar.com/avatar/7e41acaa8f6a0e0f5a7c645e93add55a.jpg?s=120&d=mm&r=g)
On Sep 30, 2015, at 18:08, MRAB <python@mrabarnett.plus.com> wrote:
It's only just occurred to me that there's a small inconsistency here. The "?.", "?[" and "?(" will short-circuit on None, whereas the "??" will short-circuit on non-None.
Would that cause any confusion in practice?
I noticed this when I was trying to write out grammar, sample ASTs, and sample bytecode for these things. I went searching the thread and saw no one had pointed it out. I went through docs and blogs for other languages, and didn't see anyone pointing out, complaining about, or offering to clear up any confusion. So I figured I wouldn't mention it, and see if anyone else even noticed. The fact that an experienced programmer like you didn't even notice it until after a zillion messages over a span of weeks, and apparently nobody else did at all, seems to imply that there's no dangerously misleading intuitive parallel here. (By the way, I have a feeling that including ?= will increase the risk of confusion, but I have no idea where that feeling comes from, so it may just be random noise in my head, maybe because I like ?. but don't particularly like ?= or something...)
![](https://secure.gravatar.com/avatar/3dd475b8aaa5292d74cb0c3f76c3f196.jpg?s=120&d=mm&r=g)
Andrew Barnert via Python-ideas <python-ideas@python.org> writes:
On Sep 30, 2015, at 18:08, MRAB <python@mrabarnett.plus.com> wrote:
It's only just occurred to me that there's a small inconsistency here. The "?.", "?[" and "?(" will short-circuit on None, whereas the "??" will short-circuit on non-None.
Would that cause any confusion in practice?
I noticed this when I was trying to write out grammar, sample ASTs, and sample bytecode for these things. I went searching the thread and saw no one had pointed it out. I went through docs and blogs for other languages, and didn't see anyone pointing out, complaining about, or offering to clear up any confusion. So I figured I wouldn't mention it, and see if anyone else even noticed.
How is it worse than the fact that and short-circuits on true whereas or short-circuits on false? Short-circuiting logically applies to the case that *can* be short-circuited. For the AST issue, I'm curious as to what you ended up doing about the whole-atom_expr nature of the short-circuiting and the fact that ASTs don't currently represent an atom_expr as a single object containing a list of subscript/attribute/call items?
![](https://secure.gravatar.com/avatar/7e41acaa8f6a0e0f5a7c645e93add55a.jpg?s=120&d=mm&r=g)
On Sep 30, 2015, at 21:19, Random832 <random832@fastmail.com> wrote:
Andrew Barnert via Python-ideas <python-ideas@python.org> writes:
On Sep 30, 2015, at 18:08, MRAB <python@mrabarnett.plus.com> wrote:
It's only just occurred to me that there's a small inconsistency here. The "?.", "?[" and "?(" will short-circuit on None, whereas the "??" will short-circuit on non-None.
Would that cause any confusion in practice?
I noticed this when I was trying to write out grammar, sample ASTs, and sample bytecode for these things. I went searching the thread and saw no one had pointed it out. I went through docs and blogs for other languages, and didn't see anyone pointing out, complaining about, or offering to clear up any confusion. So I figured I wouldn't mention it, and see if anyone else even noticed.
How is it worse than the fact that and short-circuits on true whereas or short-circuits on false?
Why are you asking me how it's worse when my conclusion was that it's fine, in the sentence right after the part you quoted?
For the AST issue, I'm curious as to what you ended up doing about the whole-atom_expr nature of the short-circuiting and the fact that ASTs don't currently represent an atom_expr as a single object containing a list of subscript/attribute/call items?
I posted ?. examples earlier; I don't want to repeat the whole thing (especially after Guido pointed out that it probably wasn't helping anyone who didn't already get it). But briefly, the AST doesn't have to represent the short-circuiting here, any more than it does anywhere else that short-circuits; it just adds an uptalk flag to each Attribute node. At code generation time, any Attribute node that has uptalk=True has a JUMP_IF_NONE to after the primary-or-call (leaving the None attrib value on the stack) after the LOAD_ATTR. (Or, if you don't want to add a new bytecode, it has a string of three ops that do the equivalent.) The same works for ?[]. For ?(), I'm not sure what the desired semantics are, and for ?? it seemed obviously trivial so I didn't bother.
![](https://secure.gravatar.com/avatar/3dd475b8aaa5292d74cb0c3f76c3f196.jpg?s=120&d=mm&r=g)
(Towards the bottom of this post I ask the question of *why* it's a bad thing for uptalk to "escape parentheses" - mentioning it up here so as to not bury the lead.) On Thu, Oct 1, 2015, at 01:59, Andrew Barnert wrote:
I posted ?. examples earlier; I don't want to repeat the whole thing (especially after Guido pointed out that it probably wasn't helping anyone who didn't already get it). But briefly, the AST doesn't have to represent the short-circuiting here, any more than it does anywhere else that short-circuits; it just adds an uptalk flag to each Attribute node. At code generation time, any Attribute node that has uptalk=True has a JUMP_IF_NONE to after the primary-or-call (leaving the None attrib value on the stack) after the LOAD_ATTR. (Or, if you don't want to add a new bytecode, it has a string of three ops that do the equivalent.)
Four ops, actually - DUP_TOP LOAD_CONST(None) COMPARE_OP(is) POP_JUMP_IF_TRUE - unless I'm missing a more efficient way to do it. And it has to jump an arbitrary distance forward (however many calls, attributes, or subscripts are in the expression), not just "to after the one after". And the problem is, the AST can't differentiate (a.b).c from a.b.c, whereas (a?.b).c is *semantically different* from a?.b.c - I think a new AST structure is therefore necessary. At the very least you'd need something in the node corresponding to the uptalk to say _where_ to jump (i.e. how many levels to escape from). If designing an AST structure from scratch for such a language I think a?.b.c would absolutely not be represented as any kind of Y(X('a', 'b'), 'c') because it doesn't *make sense*. I was just wondering how you tackled this problem.
The same works for ?[]. For ?(), I'm not sure what the desired semantics are
The same as for the others, AIUI.
, and for ?? it seemed obviously trivial so I didn't bother.
My understanding of how the bytecode would work: a?.b.c.d?.e.f.g = LOAD_[whatever] a JUMP_IF_NONE * LOAD_ATTR b LOAD_ATTR c LOAD_ATTR d JUMP_IF_NONE * LOAD_ATTR e LOAD_ATTR f LOAD_ATTR g * this position is the target of both jumps (a?.b.c).d?.e.f.g LOAD_[whatever] a JUMP_IF_NONE * LOAD_ATTR b LOAD_ATTR c * target of first jump LOAD_ATTR d JUMP_IF_NONE ** LOAD_ATTR e LOAD_ATTR f LOAD_ATTR g ** target of second jump P.S. Now that I spell it out like that, it occurs to me that this second case is *not actually that useful*. You're guaranteeing an exception if a is None, which defeats the purpose of using uptalk on a?... at all. (It does change whether or not the side effects of a subscript/call arguments get evaluated, but it's not clear that this justifies the added complexity.) Maybe the uptalk should instead be apply to _any_ chain (by left operands) of AST Attribute/Subscript/Call nodes regardless of whether or not there are parentheses around them. It's been stated repeatedly that uptalk shouldn't "escape parentheses", but no-one's clearly stated *why* that should be the case.
![](https://secure.gravatar.com/avatar/5ce43469c0402a7db8d0cf86fa49da5a.jpg?s=120&d=mm&r=g)
On 2015-10-01 19:16, Random832 wrote:
(Towards the bottom of this post I ask the question of *why* it's a bad thing for uptalk to "escape parentheses" - mentioning it up here so as to not bury the lead.)
On Thu, Oct 1, 2015, at 01:59, Andrew Barnert wrote:
I posted ?. examples earlier; I don't want to repeat the whole thing (especially after Guido pointed out that it probably wasn't helping anyone who didn't already get it). But briefly, the AST doesn't have to represent the short-circuiting here, any more than it does anywhere else that short-circuits; it just adds an uptalk flag to each Attribute node. At code generation time, any Attribute node that has uptalk=True has a JUMP_IF_NONE to after the primary-or-call (leaving the None attrib value on the stack) after the LOAD_ATTR. (Or, if you don't want to add a new bytecode, it has a string of three ops that do the equivalent.)
Four ops, actually - DUP_TOP LOAD_CONST(None) COMPARE_OP(is) POP_JUMP_IF_TRUE - unless I'm missing a more efficient way to do it. And it has to jump an arbitrary distance forward (however many calls, attributes, or subscripts are in the expression), not just "to after the one after".
And the problem is, the AST can't differentiate (a.b).c from a.b.c, whereas (a?.b).c is *semantically different* from a?.b.c - I think a new AST structure is therefore necessary. At the very least you'd need something in the node corresponding to the uptalk to say _where_ to jump (i.e. how many levels to escape from).
If designing an AST structure from scratch for such a language I think a?.b.c would absolutely not be represented as any kind of Y(X('a', 'b'), 'c') because it doesn't *make sense*. I was just wondering how you tackled this problem.
The same works for ?[]. For ?(), I'm not sure what the desired semantics are
The same as for the others, AIUI.
, and for ?? it seemed obviously trivial so I didn't bother.
My understanding of how the bytecode would work:
a?.b.c.d?.e.f.g =
LOAD_[whatever] a JUMP_IF_NONE * LOAD_ATTR b LOAD_ATTR c LOAD_ATTR d JUMP_IF_NONE * LOAD_ATTR e LOAD_ATTR f LOAD_ATTR g * this position is the target of both jumps
(a?.b.c).d?.e.f.g
LOAD_[whatever] a JUMP_IF_NONE * LOAD_ATTR b LOAD_ATTR c * target of first jump LOAD_ATTR d JUMP_IF_NONE ** LOAD_ATTR e LOAD_ATTR f LOAD_ATTR g ** target of second jump
P.S.
Now that I spell it out like that, it occurs to me that this second case is *not actually that useful*. You're guaranteeing an exception if a is None, which defeats the purpose of using uptalk on a?... at all.
It's _not_ guaranteed that there'll be an exception if a is None; None does have _some_ attributes.
(It does change whether or not the side effects of a subscript/call arguments get evaluated, but it's not clear that this justifies the added complexity.)
Maybe the uptalk should instead be apply to _any_ chain (by left operands) of AST Attribute/Subscript/Call nodes regardless of whether or not there are parentheses around them. It's been stated repeatedly that uptalk shouldn't "escape parentheses", but no-one's clearly stated *why* that should be the case.
'and' and 'or' don't escape parentheses. I'd take it as a general rule that short-circuiting doesn't escape parentheses.
![](https://secure.gravatar.com/avatar/3dd475b8aaa5292d74cb0c3f76c3f196.jpg?s=120&d=mm&r=g)
On Thu, Oct 1, 2015, at 14:36, MRAB wrote:
It's _not_ guaranteed that there'll be an exception if a is None; None does have _some_ attributes.
Well, it hasn't got a "d" attribute, but point taken. However, wanting to build expressions like this that will sometimes operate on the attributes of None and other times operate on the attributes of the non-None object normally expected to be present seems like an *extremely* obscure thing to want.
'and' and 'or' don't escape parentheses. I'd take it as a general rule that short-circuiting doesn't escape parentheses.
Sure they do. In what way is (a and b) and c different from a and b and c? Heck, (a and b) and (c and d) even compiles to the same bytecode as other groupings [demonstrating it has the same actual semantics] *even though it is a different AST*. But even if it didn't, it would just be jumping to another jump opcode. It doesn't escape being mixed with a different kind of expression, which is sometimes accomplished with parentheses, but that's not really the same thing.
![](https://secure.gravatar.com/avatar/5615a372d9866f203a22b2c437527bbb.jpg?s=120&d=mm&r=g)
On Thu, Oct 01, 2015 at 02:50:07PM -0400, Random832 wrote:
On Thu, Oct 1, 2015, at 14:36, MRAB wrote: [...]
'and' and 'or' don't escape parentheses. I'd take it as a general rule that short-circuiting doesn't escape parentheses.
Sure they do. In what way is (a and b) and c different from a and b and c? Heck, (a and b) and (c and d) even compiles to the same bytecode as other groupings [demonstrating it has the same actual semantics] *even though it is a different AST*. But even if it didn't, it would just be jumping to another jump opcode.
I don't see what this has to do with null aware attribute access. If I wrote: (spam?.eggs.cheese).__class__ the obvious intention is that it should evaluate like: temp = spam?.eggs.cheese temp.__class__ only without the use of a temporary name. -- Steve
![](https://secure.gravatar.com/avatar/3dd475b8aaa5292d74cb0c3f76c3f196.jpg?s=120&d=mm&r=g)
On Thu, Oct 1, 2015, at 15:48, Steven D'Aprano wrote:
I don't see what this has to do with null aware attribute access.
If I wrote:
(spam?.eggs.cheese).__class__
the obvious intention is that it should evaluate like:
temp = spam?.eggs.cheese temp.__class__
Why would you need to do that, though? What's the practical use case for this, that you need to do it frequently enough to want a way to do it without using a temporary? Or do you think that, as the thing that it short-circuits to the end of, "same atom_expr unbroken by parentheses" [a concept which, I remind you, *does not exist* in AST] is somehow simpler or easier to explain than "same chain of Attribute/Call/Subscript operations"?
![](https://secure.gravatar.com/avatar/5ce43469c0402a7db8d0cf86fa49da5a.jpg?s=120&d=mm&r=g)
On 2015-10-01 19:50, Random832 wrote:
On Thu, Oct 1, 2015, at 14:36, MRAB wrote:
It's _not_ guaranteed that there'll be an exception if a is None; None does have _some_ attributes.
Well, it hasn't got a "d" attribute, but point taken. However, wanting to build expressions like this that will sometimes operate on the attributes of None and other times operate on the attributes of the non-None object normally expected to be present seems like an *extremely* obscure thing to want.
'and' and 'or' don't escape parentheses. I'd take it as a general rule that short-circuiting doesn't escape parentheses.
Sure they do. In what way is (a and b) and c different from a and b and c? Heck, (a and b) and (c and d) even compiles to the same bytecode as other groupings [demonstrating it has the same actual semantics] *even though it is a different AST*. But even if it didn't, it would just be jumping to another jump opcode.
If a is falsy, it short-circuits "a and b". The parenthesised expression returns a falsy result. That falsy result then short-circuits "(a and b) and c". It happens to show the same behaviour as "a and b and c" and can be optimised to that. Well, that's my opinion, anyway!
It doesn't escape being mixed with a different kind of expression, which is sometimes accomplished with parentheses, but that's not really the same thing.
![](https://secure.gravatar.com/avatar/3dd475b8aaa5292d74cb0c3f76c3f196.jpg?s=120&d=mm&r=g)
On Thu, Oct 1, 2015, at 16:07, MRAB wrote:
If a is falsy, it short-circuits "a and b".
The parenthesised expression returns a falsy result.
That falsy result then short-circuits "(a and b) and c".
It happens to show the same behaviour as "a and b and c" and can be optimised to that.
Er... that's what a and b and c *is*. The 'and' operator is a left-associative binary operator. ((a and b) and c) is literally the same AST as (a and b and c). Being optimized to "short-circuit the whole thing" is an optimization for both of them. The naive way you describe of evaluating each one in turn is also the same for both of them. They are in fact both being optimized to (a and (b and c)) [rather, to the same byte code that a 'naive' implementation would still generate for that expression].
![](https://secure.gravatar.com/avatar/345fd2a0955fbab272da7ee594cbe7c1.jpg?s=120&d=mm&r=g)
I've been thinking of practical uses for this new None aware operator and although I really like the idea, I believe this may become too weird to use if some things are not taken care of. Are all of those things meant to work? (or am I just crazy?) -- assuming C#-like syntax: for el in re.match(foo, bar)?.groups() ?? []: print(el.upper()) ------ # Assuming this function for readability def is_useful(x): return x is not None if not foo?: # Should read like "not is_useful(foo)" print('foo is None') # With this syntax it is easier to correct all those pieces of code "if not x:" where it should be "if x is None:" if foo??: # A shorter version possibly meaning "foo ?? True" or "foo is None" print('foo is still None') ----- # Are operators other than [] and () allowed? bar = foo? + 1 # For "foo?.__add__(1)" it would work, but for the realistic # "type(foo)?.__add__(foo, 1)" it does not translate correctly bar = foo ?+ 1 # Another spelling. Is this any better?? bar = foo? / baz? / 2 # How would this work? '(foo / baz) / 2' or 'foo / (bar / 2)' ----- Also for correctness, the shouldn't the '?=' operator be '??='
![](https://secure.gravatar.com/avatar/7e41acaa8f6a0e0f5a7c645e93add55a.jpg?s=120&d=mm&r=g)
On Oct 1, 2015, at 14:00, João Bernardo <jbvsmo@gmail.com> wrote:
I've been thinking of practical uses for this new None aware operator and although I really like the idea, I believe this may become too weird to use if some things are not taken care of. Are all of those things meant to work? (or am I just crazy?) -- assuming C#-like syntax:
for el in re.match(foo, bar)?.groups() ?? []: print(el.upper())
Yes.
------
# Assuming this function for readability def is_useful(x): return x is not None
if not foo?: # Should read like "not is_useful(foo)" print('foo is None') # With this syntax it is easier to correct all those pieces of code "if not x:" where it should be "if x is None:"
No. Neither this, nor any of your other examples below, are meant to work. The proposal adds a binary operator ??, three primary operations ?., ?[], and ?(), and possibly a new assignment ?=. It does not add a unary postfix operator ?, and you can't infer one from any of those forms any more than you can infer a unary postfix + from +=. While there was some discussion very early in the thread about whether a unary postfix ? operator could be used instead of all these separate things, but it was quickly realized that it doesn't express short-circuiting right for null-conditional access, doesn't make any sense at all for binary null coalescing, and does the wrong thing for null-conditional assignment, so Guido soundly rejected it and nobody disagreed. Guido also raised the question of whether we should uptalk most operators, skipping only the ones that make no sense, or only update a few that have compelling use cases. People had a bit of fun exploring the former, but I think everyone agreed that the latter makes a lot more sense, and that's what's in the preliminary PEP. So, under the proposal under discussions, this example, and your other examples, are all syntax errors, just like "if foo+:" and "if foo++:" are syntax errors.
if foo??: # A shorter version possibly meaning "foo ?? True" or "foo is None" print('foo is still None')
----- # Are operators other than [] and () allowed?
bar = foo? + 1 # For "foo?.__add__(1)" it would work, but for the realistic # "type(foo)?.__add__(foo, 1)" it does not translate correctly bar = foo ?+ 1 # Another spelling. Is this any better??
bar = foo? / baz? / 2 # How would this work? '(foo / baz) / 2' or 'foo / (bar / 2)'
-----
Also for correctness, the shouldn't the '?=' operator be '??='
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
![](https://secure.gravatar.com/avatar/f3ba3ecffd20251d73749afbfa636786.jpg?s=120&d=mm&r=g)
On 1 October 2015 at 12:39, Andrew Barnert via Python-ideas <python-ideas@python.org> wrote:
On Sep 30, 2015, at 18:08, MRAB <python@mrabarnett.plus.com> wrote:
It's only just occurred to me that there's a small inconsistency here. The "?.", "?[" and "?(" will short-circuit on None, whereas the "??" will short-circuit on non-None.
Would that cause any confusion in practice?
I noticed this when I was trying to write out grammar, sample ASTs, and sample bytecode for these things. I went searching the thread and saw no one had pointed it out. I went through docs and blogs for other languages, and didn't see anyone pointing out, complaining about, or offering to clear up any confusion. So I figured I wouldn't mention it, and see if anyone else even noticed.
The fact that an experienced programmer like you didn't even notice it until after a zillion messages over a span of weeks, and apparently nobody else did at all, seems to imply that there's no dangerously misleading intuitive parallel here.
(By the way, I have a feeling that including ?= will increase the risk of confusion, but I have no idea where that feeling comes from, so it may just be random noise in my head, maybe because I like ?. but don't particularly like ?= or something...)
Because unlike "?.", "?[" and "?(", "?=" would also shortcircuit on "if not None" if expanded as a ternary expression: target = target if target is not None else default However, it's possible to make it consistent by instead expanding it as: if target is None: target = default As a result, one interesting way of looking at this problem is to ask: what if we *only* offered the conditional operations, *without* offering a binary null-coalescing operator? That is, we could allow conditional assignment: data ?= [] headers ?= {} arg ?= make_default() With the meaning: if data is None: data = [] if headers is None: headers = {} if arg is None: arg = make_default() That would deal directly with the idiomatic case of handling a default argument of "None" and replacing it with a mutable container or expensive to calculate default value. Multiple levels of coalescence would need to be spelled out as multiple statements rather than as chained binary operations (in this case, I think putting it all on one line helps make the "first non-None value wins" semantics clearer): title ?= user_title; title ?= local_default_title; title ?= global_default_title The semantics of dict.setdefault() could also potentially be made clearer, as "d[key] ?= make_default()" could become a short circuiting equivalent of "dict.setdefault(k, make_default())": try: d[key] except KeyError: d[key] = make_default() Custom sentinels would still need to be spelled out with an if statement: if arg is _sentinel: arg = make_default() If they were offered, conditional attribute access, conditional item lookup and conditional calls would use the same "LHS is None" check, but in a ternary expression rather than an if statement, with the following: title?.upper() person?['name'] func?(arg) being equivalent to: None if title is None else title.upper() None if person is None else person['name'] None if func is None else func(arg) Regards, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
![](https://secure.gravatar.com/avatar/e1f5f984cbc32a8ba5d0f074d2f1cf19.jpg?s=120&d=mm&r=g)
On 10/01/2015 02:59 AM, Nick Coghlan wrote:
As a result, one interesting way of looking at this problem is to ask: what if we *only* offered the conditional operations, *without* offering a binary null-coalescing operator?
How languages treat None, null, and nil, are very fundamental aspects of how they work. For example I noticed that if we add an None specific 'or' operator, we may want a None specific 'and' operator too. And in order to use those in conditional statements, it makes sense to have None specific 'if' and 'while'. So it may be a slippery slope. (Or they may be good to have. I'm very undecided still.)
That is, we could allow conditional assignment:
data ?= [] headers ?= {} arg ?= make_default()
With the meaning:
if data is None: data = [] if headers is None: headers = {} if arg is None: arg = make_default()
Is the above three statements, three expressions, or one long expression? I was contemplating the possibility of having a variation of "is" as a way to test for not-null, but it leads down the same path. Once you have one None specific bool operator, the rest are needed to make full use of it. An Observation... In many discussions here, the same shortcut problem keeps coming up when ever we don't want to evaluate a functions arguments too early. So maybe what we really need is a syntax for a conditional partial where some arguments aren't evaluated until asked for. The ?( is almost that, but some way to short cut the arguments would still be needed. One possibility is to have them all as optional only, and just not ask for them. And to go out on a limb... ;-) Another possibility is to have a *special magic callable* that when called skips the argument evaluation and returns None. NoneCall(these, args, are, never, evaluated) In this case the Magic is underwhelming as it does absolutely nothing except return None. I think I can live with that. Then a special method on objects, obj __cond__call__() can return a NoneCall to skip the arguments on the right side of ?(, or return another callable to use them. This is a bit like decorators in that to use the arguments you need to return a callable. So I think maybe the @ symbol would be better. I can see this being used in decorators to apply decorators conditionally. The case of a Null default might be done by just having a conditional call on None, unpack the arguments. So it's __cond_call__ method if it had one, would be an identity function. value = obj@(default) # if obj is None: value = default # if obj has __cond_call__ method # it calls it with the arguments. # else TypeError So the '@(', or '?(' if preferred, may be the only one we need. The rest might be implementable with it by defining __cond_call__ methods on the objects. Cheers, Ron
![](https://secure.gravatar.com/avatar/3dd475b8aaa5292d74cb0c3f76c3f196.jpg?s=120&d=mm&r=g)
On Thu, Oct 1, 2015, at 16:57, Ron Adam wrote:
And to go out on a limb... ;-)
Another possibility is to have a *special magic callable* that when called skips the argument evaluation and returns None.
That's dangerous talk indeed. Special magic callables are Lisp territory. ;) And I don't even know how you'd implement it efficiently without them being known at compile-time. I guess at *every* callsite you could test if the callable is magic, and if it is evaluate the arguments, and if it's not just pass in a lambda that will return the arguments. But you've got to generate those lambdas for *all* callsites, even the vast majority that will never be a magic callable. What if the callable only wants *some* of the arguments? Hey, if this had existed back then the ternary operator could have been a normal function - instead of (b() if a else c()) just do iif(a, b(), c()). And is this going to be fully general? I.e. should this be supported for regular operators? If __add__ is magic does + do this, for example?
![](https://secure.gravatar.com/avatar/7e41acaa8f6a0e0f5a7c645e93add55a.jpg?s=120&d=mm&r=g)
On Oct 1, 2015, at 14:12, Random832 <random832@fastmail.com> wrote:
On Thu, Oct 1, 2015, at 16:57, Ron Adam wrote: And to go out on a limb... ;-)
Another possibility is to have a *special magic callable* that when called skips the argument evaluation and returns None.
That's dangerous talk indeed. Special magic callables are Lisp territory. ;)
Despite the smiley at the end, I think that second sentence is exactly the issue here. This is basically fexprs, with the same pros and cons. The language doesn't have to provide new short-circuiting operators like if-else or ?? or other kinds of flow-control expressions because the end user can write them himself easily. On the other hand, because the end user can write flow-control expressions himself, every project ends up being written in a similar but semantically different language. (Of course hardcore Lisp advocates claim that con is also a pro: it means you can spend the first 80% of a project building up a language that makes the application trivial, and the last 20% coding and debugging the application. Sure, anyone who wasn't involved in that first 80% is never going to be able to understand the code you wrote in the last 20%, but really, who else in the world is smart enough to grok your brilliant code anyway, so where's the problem? Fortunately, no one has outlawed Lisp, so those people can continue to use it and don't have to use horribly restrictive languages like Python or Scala like all of us dumb sheeple, so we can ignore that.)
And I don't even know how you'd implement it efficiently without them being known at compile-time.
I guess at *every* callsite you could test if the callable is magic, and if it is evaluate the arguments, and if it's not just pass in a lambda that will return the arguments. But you've got to generate those lambdas for *all* callsites, even the vast majority that will never be a magic callable.
What if the callable only wants *some* of the arguments? Hey, if this had existed back then the ternary operator could have been a normal function - instead of (b() if a else c()) just do iif(a, b(), c()).
And is this going to be fully general? I.e. should this be supported for regular operators? If __add__ is magic does + do this, for example?
![](https://secure.gravatar.com/avatar/e1f5f984cbc32a8ba5d0f074d2f1cf19.jpg?s=120&d=mm&r=g)
On 10/01/2015 04:12 PM, Random832 wrote:
On Thu, Oct 1, 2015, at 16:57, Ron Adam wrote:
And to go out on a limb...;-)
Another possibility is to have a*special magic callable* that when called skips the argument evaluation and returns None.
That's dangerous talk indeed. Special magic callables are Lisp territory.;)
It's also lambda calculus territory.
And I don't even know how you'd implement it efficiently without them being known at compile-time.
I guess at*every* callsite you could test if the callable is magic, and if it is evaluate the arguments, and if it's not just pass in a lambda that will return the arguments. But you've got to generate those lambdas for*all* callsites, even the vast majority that will never be a magic callable.
I realized this evening the parser doesn't need to know at parse time, and the object doesn't need to be special. It's the syntax that gives it the specialness, not the object. So what I was thinking is still possible, but it would work more like the other suggestions. If you look at byte code generated for a function call...
def bar(x, y): ... return x + y ... def foo(x, y): ... return bar(x+1, y+1) ... dis(foo) 2 0 LOAD_GLOBAL 0 (bar) 3 LOAD_FAST 0 (x) 6 LOAD_CONST 1 (1) 9 BINARY_ADD 10 LOAD_FAST 1 (y) 13 LOAD_CONST 1 (1) 16 BINARY_ADD 17 CALL_FUNCTION 2 (2 positional, 0 keyword pair) 20 RETURN_VALUE
You will notice the function is loaded on the stack *before* the argument expressions are evaluated. It won't require generating lamba expressions, just a conditional jump where '?(' is used. So no, it won't be required at every call site. Something roughly like ... (presume the index's are adjusted correctly) 2 0 LOAD_GLOBAL 0 (bar) #-- inserted only if '?(' is used -------------- POP_JUMP_IF_NOT_NOCALL 3 LOAD_CONST 0 (None) JUMP_FORWARD 8 (to 20) #----------------------------------------------- 3 LOAD_FAST 0 (x) 6 LOAD_CONST 1 (1) 9 BINARY_ADD 10 LOAD_FAST 1 (y) 13 LOAD_CONST 1 (1) 16 BINARY_ADD 17 CALL_FUNCTION 2 (2 positional, 0 keyword pair) 20 RETURN_VALUE So the parser doesn't need to know if it's the NoneCall object at parse time, it just needs to know to check for it, and the ?( syntax can tell it to do that only where it's needed. There really isn't anything special about the NoneCall object, it's just a reserved builtin object. And adding the check for it isn't all the special either. It's the behavior that is special because of the ?( sytnax. I was thinking it could call a __cond_call__ method on the object. That extra indirect call could allow objects to specify how they respond to ?( conditional calls. It's just requires an extra LOAD_ATTR and call it with no arguments in the bytecode. The case of unpacking the args if the object is None would require an additional check for None and to load an identity function in it's place or could be done by adding a __cond_call__ method (if that is how ?( will work) to None that is an identity function. value = obj?(default) #None?(default) --> identity(default) Think of these as moving conditions that would otherwise be elsewhere to a more convenient location. So I don't expect them to be much slower than the equivalent code that does the same thing. A __cond_call__ method works because it can't conflict with __call__. So it avoids any conflicts that might occur by calling __call__ by mistake. The point is that a conditional call syntax could also fill the need of the or None operator. The specific details may be a bit different than what I describe here, but I think it's doable.
What if the callable only wants*some* of the arguments? Hey, if this had existed back then the ternary operator could have been a normal function - instead of (b() if a else c()) just do iif(a, b(), c()).
Only wanting some arguments would be much harder. It might be done by splitting the arguments into nested ?( calls similar to how we apply multiple decorators. I don't expect it to be pretty or practical, so lets just not go there for now.
And is this going to be fully general? I.e. should this be supported for regular operators? If __add__ is magic does + do this, for example?
No, it would only work with ?( syntax. Cheers, Ron
![](https://secure.gravatar.com/avatar/7e41acaa8f6a0e0f5a7c645e93add55a.jpg?s=120&d=mm&r=g)
On Oct 2, 2015, at 03:01, Ron Adam <ron3200@gmail.com> wrote:
On 10/01/2015 04:12 PM, Random832 wrote:
On Thu, Oct 1, 2015, at 16:57, Ron Adam wrote:
And to go out on a limb...;-)
Another possibility is to have a*special magic callable* that when called skips the argument evaluation and returns None.
That's dangerous talk indeed. Special magic callables are Lisp territory.;)
It's also lambda calculus territory.
And I don't even know how you'd implement it efficiently without them being known at compile-time.
I guess at*every* callsite you could test if the callable is magic, and if it is evaluate the arguments, and if it's not just pass in a lambda that will return the arguments. But you've got to generate those lambdas for*all* callsites, even the vast majority that will never be a magic callable.
I realized this evening the parser doesn't need to know at parse time, and the object doesn't need to be special. It's the syntax that gives it the specialness, not the object. So what I was thinking is still possible, but it would work more like the other suggestions.
But if you've already got special handling for ?(), you don't need this NoCall object at all. If bar is None, ?() just skips the call. Compare the bytecode for your version:
2 0 LOAD_GLOBAL 0 (bar)
#-- inserted only if '?(' is used -------------- POP_JUMP_IF_NOT_NOCALL 3 LOAD_CONST 0 (None) JUMP_FORWARD 8 (to 20) #-----------------------------------------------
... to the simple ?() check version: 2 0 LOAD_GLOBAL 0 (bar) #-- inserted only if '?(' is used JUMP_IF_NONE 8 (to 20) #-------------------------------- That's it. (And if you don't want to add a JUMP_IF_NONE opcode, you just DUP, compare to None, and POP_JUMP_IF_TRUE.) Just like ?. and ?[], the LOAD_* is followed by a JUMP_IF_NONE to the end of the first call (or the end of the entire primary if there is no call, but that case can't come up for ?() because it is a call), and nothing else has to be done.
The case of unpacking the args if the object is None would require an additional check for None and to load an identity function in it's place or could be done by adding a __cond_call__ method (if that is how ?( will work) to None that is an identity function.
Why do you want to do this? And what does it even mean to "unpack the args" here? Does the identity function return a tuple and a dict for the positional and keyword functions respectively? When would you ever want that? Also, to call the identity function (or anything else) on the args requires evaluating them, which defeats the entire purpose of short-circuiting evaluation of the args. And, even if the idea of a universal identity function made sense here, why even call it instead of just not calling anything? What's the benefit from that extra work?
![](https://secure.gravatar.com/avatar/3dd475b8aaa5292d74cb0c3f76c3f196.jpg?s=120&d=mm&r=g)
On Fri, Oct 2, 2015, at 06:01, Ron Adam wrote:
On 10/01/2015 04:12 PM, Random832 wrote:
On Thu, Oct 1, 2015, at 16:57, Ron Adam wrote:
And to go out on a limb...;-)
Another possibility is to have a*special magic callable* that when called skips the argument evaluation and returns None.
That's dangerous talk indeed. Special magic callables are Lisp territory.;)
It's also lambda calculus territory.
Does lambda calculus really have a notion of two types of function that are called with the same syntax but one receives the values as they have been evaluated and the other receives some abstract representation of how to get them if it needs them? That doesn't even make any sense. Does it even have a notion of side effects and therefore when/whether things are evaluated if they are not needed? Not everything in Lisp is in lambda calculus.
I realized this evening the parser doesn't need to know at parse time, and the object doesn't need to be special. It's the syntax that gives it the specialness, not the object. So what I was thinking is still possible, but it would work more like the other suggestions.
If you look at byte code generated for a function call...
You will notice the function is loaded on the stack *before* the argument expressions are evaluated.
It won't require generating lamba expressions, just a conditional jump where '?(' is used. So no, it won't be required at every call site.
What does this have to do with the idea of a magic callable? If this is implemented with a magic callable then A) *any* callable might be a magic callable and B) the idea original idea strongly implied a generalized notion of magic callables, of which NoneCall would be only one example. How do you pass the arguments to the *other* magic callables that *do* [maybe] evaluate them? You've basically just explained how the bytecode works for ?( if it is *not* implemented with a magic callable. So how does making it NoneCall instead of just None improve anything?
![](https://secure.gravatar.com/avatar/db5b03704c129196a4e9415e55413ce6.jpg?s=120&d=mm&r=g)
On October 2, 2015 7:54:46 AM CDT, Random832 <random832@fastmail.com> wrote:
On Fri, Oct 2, 2015, at 06:01, Ron Adam wrote:
On 10/01/2015 04:12 PM, Random832 wrote:
On Thu, Oct 1, 2015, at 16:57, Ron Adam wrote:
And to go out on a limb...;-)
Another possibility is to have a*special magic callable* that when called skips the argument evaluation and returns None.
That's dangerous talk indeed. Special magic callables are Lisp territory.;)
It's also lambda calculus territory.
Does lambda calculus really have a notion of two types of function that are called with the same syntax but one receives the values as they have been evaluated and the other receives some abstract representation of how to get them if it needs them? That doesn't even make any sense. Does it even have a notion of side effects and therefore when/whether things are evaluated if they are not needed?
Indeed. Lambda calculus is untyped...
Not everything in Lisp is in lambda calculus.
I realized this evening the parser doesn't need to know at parse time, and the object doesn't need to be special. It's the syntax that gives it the specialness, not the object. So what I was thinking is still possible, but it would work more like the other suggestions.
If you look at byte code generated for a function call...
You will notice the function is loaded on the stack *before* the argument expressions are evaluated.
It won't require generating lamba expressions, just a conditional jump where '?(' is used. So no, it won't be required at every call site.
What does this have to do with the idea of a magic callable? If this is implemented with a magic callable then A) *any* callable might be a magic callable and B) the idea original idea strongly implied a generalized notion of magic callables, of which NoneCall would be only one example. How do you pass the arguments to the *other* magic callables that *do* [maybe] evaluate them?
You've basically just explained how the bytecode works for ?( if it is *not* implemented with a magic callable. So how does making it NoneCall instead of just None improve anything? _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
-- Sent from my Nexus 5 with K-9 Mail. Please excuse my brevity.
![](https://secure.gravatar.com/avatar/e1f5f984cbc32a8ba5d0f074d2f1cf19.jpg?s=120&d=mm&r=g)
On 10/02/2015 07:54 AM, Random832 wrote:
On Fri, Oct 2, 2015, at 06:01, Ron Adam wrote:
On 10/01/2015 04:12 PM, Random832 wrote:
On Thu, Oct 1, 2015, at 16:57, Ron Adam wrote:
And to go out on a limb...;-)
Another possibility is to have a*special magic callable* that when called skips the argument evaluation and returns None.
That's dangerous talk indeed. Special magic callables are Lisp territory.;)
It's also lambda calculus territory.
Does lambda calculus really have a notion of two types of function that are called with the same syntax but one receives the values as they have been evaluated and the other receives some abstract representation of how to get them if it needs them? That doesn't even make any sense. Does it even have a notion of side effects and therefore when/whether things are evaluated if they are not needed?
Not everything in Lisp is in lambda calculus.
I wasn't really comparing it to lisp, but I understood your reference to it. The comparison to lambda calculus is very loose also. BTW, in lisp (and scheme) there are a handful of special functions. One of those is the if function which avoids early evaluation of the expressions till after the condition is evaluated. It can be implemented as a normal lisp expression but not without quoting the arguments.
I realized this evening the parser doesn't need to know at parse time, and the object doesn't need to be special. It's the syntax that gives it the specialness, not the object. So what I was thinking is still possible, but it would work more like the other suggestions.
If you look at byte code generated for a function call...
You will notice the function is loaded on the stack *before* the argument expressions are evaluated.
It won't require generating lamba expressions, just a conditional jump where '?(' is used. So no, it won't be required at every call site.
What does this have to do with the idea of a magic callable?
As I said above it doesn't need to magic after all.
If this is implemented with a magic callable then A) *any* callable might be a magic callable and B) the idea original idea strongly implied a generalized notion of magic callables, of which NoneCall would be only one example. How do you pass the arguments to the *other* magic callables that *do* [maybe] evaluate them?
You've basically just explained how the bytecode works for ?( if it is *not* implemented with a magic callable. So how does making it NoneCall instead of just None improve anything?
It's not quite the same. The proposed ?( skips the arguments if the right hand side is None. None?(...) ---> None So you can't use that to supply a default directly. value = None?(default) # returns None (not useful here) To make that work, think of applying None to the arguments. Which in turn does nothing to them, they just get returned. value = None?(default) --> default But to get the inverse of that which is the original preposed ?( behavior, you need a different way to trigger that. We could use False I suppose. value = False?(default) --> None And of course.... value = other?(default) --> other(default) This is just based on the thought that a conditional ?( calling syntax could be useful in a broader scope and not just used as a None coalescing operator. So in the above, None, False, and other can be replaced by expressions. def when(x): return None if x else False value = when(cond)?(expr) # expr or None With the original proposed ?( operator the set default example is still doable, but it needs a helper function. None?(expr) --> None other?(expr) --> other(expr) # The True case def is_none(cond): return None if cond is None else lambda x:x value = is_none(value)?(default_value) Compared to value = default_value if expr is None else None I was trying to shorten it to.. value = expr?(default) But maybe that just won't work in a nice enough way. Well it's just a thought/suggestion. (shrug) Cheers, Ron
![](https://secure.gravatar.com/avatar/7e41acaa8f6a0e0f5a7c645e93add55a.jpg?s=120&d=mm&r=g)
On Oct 2, 2015, at 10:47, Ron Adam <ron3200@gmail.com> wrote:
On 10/02/2015 07:54 AM, Random832 wrote:
On Fri, Oct 2, 2015, at 06:01, Ron Adam wrote:
On 10/01/2015 04:12 PM, Random832 wrote: On Thu, Oct 1, 2015, at 16:57, Ron Adam wrote:
And to go out on a limb...;-)
Another possibility is to have a*special magic callable* that when called skips the argument evaluation and returns None.
That's dangerous talk indeed. Special magic callables are Lisp territory.;)
It's also lambda calculus territory.
Does lambda calculus really have a notion of two types of function that are called with the same syntax but one receives the values as they have been evaluated and the other receives some abstract representation of how to get them if it needs them? That doesn't even make any sense. Does it even have a notion of side effects and therefore when/whether things are evaluated if they are not needed?
Not everything in Lisp is in lambda calculus.
I wasn't really comparing it to lisp, but I understood your reference to it. The comparison to lambda calculus is very loose also.
The comparison to Lisp is very good. Lisp has the exact concept you're asking for: a way to define a function that receives the expressions used as its arguments rather than their evaluated values. The comparison to lambda calculus doesn't even make sense. It does even have the concept of the string of lambda calculus symbols (or any other parallel) as a thing that can be passed around.
BTW, in lisp (and scheme) there are a handful of special functions. One of those is the if function which avoids early evaluation of the expressions till after the condition is evaluated. It can be implemented as a normal lisp expression but not without quoting the arguments.
Lisp and scheme also let you define new special functions that automatically get the quoted form of their arguments. (Modern Lisps don't usually have fexprs, but they have macros and special-forms, which are close enough for this discussion, I think.) And that's exactly what you're asking for here. The fact that you can define an if special form that doesn't require the caller to quote the arguments means the language doesn't need flow control expressions; you can define them yourself. There are both pros and cons to that, but the cons make it highly unsuitable for Python.
I realized this evening the parser doesn't need to know at parse time, and the object doesn't need to be special. It's the syntax that gives it the specialness, not the object. So what I was thinking is still possible, but it would work more like the other suggestions.
If you look at byte code generated for a function call...
You will notice the function is loaded on the stack *before* the argument expressions are evaluated.
It won't require generating lamba expressions, just a conditional jump where '?(' is used. So no, it won't be required at every call site.
What does this have to do with the idea of a magic callable?
As I said above it doesn't need to magic after all.
If this is implemented with a magic callable then A) *any* callable might be a magic callable and B) the idea original idea strongly implied a generalized notion of magic callables, of which NoneCall would be only one example. How do you pass the arguments to the *other* magic callables that *do* [maybe] evaluate them?
You've basically just explained how the bytecode works for ?( if it is *not* implemented with a magic callable. So how does making it NoneCall instead of just None improve anything?
It's not quite the same. The proposed ?( skips the arguments if the right hand side is None.
None?(...) ---> None
So you can't use that to supply a default directly.
value = None?(default) # returns None (not useful here)
Again, why would you want that to return default? Imagine you write spam = eggs?(default), hoping to get default if eggs is None. What are you expecting if spam is not None then?
To make that work, think of applying None to the arguments. Which in turn does nothing to them, they just get returned.
value = None?(default) --> default
Functions don't take one argument; they take zero or more positional arguments and zero or more keyword arguments. And there are probably more calls with 0 or 2 arguments than 1 in an average program (at least if you don't count the implicit self in method calls). So, what does it mean to return "the argument"? The most reasonable answer I can imagine is this: def ident(*args, **kw): return args, kw But that means that to actually use it, you have to write something like this: eggs = spam?(default) try: args, kw = default eggs = args[0] except (IndexError, ValueError): pass And even that isn't correct if the normal return value of non-None spam(default) happens to be an iterable of two elements whose first element is a non-empty sequence. And, even if it were correct, it seems a lot less readable than: eggs = spam(default) if spam is not None else default By comparison, returning None no matter what the arguments are makes sense in a way that returning the arguments doesn't: because None is a potentially usable value (especially with further chaining), unlike the arguments pair of collections.
But to get the inverse of that which is the original preposed ?( behavior, you need a different way to trigger that. We could use False I suppose.
value = False?(default) --> None
And of course....
value = other?(default) --> other(default)
This is just based on the thought that a conditional ?( calling syntax could be useful in a broader scope and not just used as a None coalescing operator.
So in the above, None, False, and other can be replaced by expressions.
def when(x): return None if x else False
value = when(cond)?(expr) # expr or None
With the original proposed ?( operator the set default example is still doable, but it needs a helper function.
None?(expr) --> None
other?(expr) --> other(expr) # The True case
def is_none(cond): return None if cond is None else lambda x:x
value = is_none(value)?(default_value)
Compared to
value = default_value if expr is None else None
I was trying to shorten it to..
value = expr?(default)
But maybe that just won't work in a nice enough way.
Well it's just a thought/suggestion. (shrug)
Cheers, Ron
![](https://secure.gravatar.com/avatar/e1f5f984cbc32a8ba5d0f074d2f1cf19.jpg?s=120&d=mm&r=g)
On 10/02/2015 02:56 PM, Andrew Barnert via Python-ideas wrote:
It's not quite the same. The proposed ?( skips the arguments if the right hand side is None.
None?(...) ---> None
So you can't use that to supply a default directly.
value = None?(default) # returns None (not useful here)
Again, why would you want that to return default?
I guess it depends on what you think it should do. None is a type, NoneType to be exact, then if like int, or float, what kind of argument should it have? '' and empty containers come to mind. Just like float and int can take '1', and error if you give it something that can't be converted to a float or int. The None?(...) syntax looks like it should do a call. Just like it would do if you did int?(...). So both the return None and the return everything are special behaviors. Possibly a None result is a bit more correct. To tell the truth I think looking at some real examples from the library would be good. How can those be improved both in how they read and how they look, and how frequent are they? This whole line of discussion started out as a very tenuous suggestion/possibility. I even said it was going out on a limb, so I really wasn't expecting to defend it as if it was a complete idea. The concept I am thing of is doable, but not in the way I was thinking at first, but I also think it doesn't fit with python very well. Cheers, Ron
![](https://secure.gravatar.com/avatar/7e41acaa8f6a0e0f5a7c645e93add55a.jpg?s=120&d=mm&r=g)
On Oct 3, 2015, at 13:27, Ron Adam <ron3200@gmail.com> wrote:
On 10/02/2015 02:56 PM, Andrew Barnert via Python-ideas wrote:
It's not quite the same. The proposed ?( skips the arguments if the right hand side is None.
None?(...) ---> None
So you can't use that to supply a default directly.
value = None?(default) # returns None (not useful here)
Again, why would you want that to return default?
I guess it depends on what you think it should do. None is a type, NoneType to be exact, then if like int, or float, what kind of argument should it have?
None isn't a type. None _has_ a type, just like 42 does. And, given that, it does exactly what I'd expect: >>> 42() TypeError: 'int' object is not callable >>> None() TypeError: 'NoneType' object is not callable >>> type(42)() 0 >>> type(None)() None >>> 42('23') TypeError: 'int' object is not callable >>> None('23') TypeError: 'NoneType' object is not callable >>> type(42)('23') 23 >>> type(None)('23') TypeError: NoneType takes no arguments Calling int('23') makes sense as a conversion constructor; calling NoneType('23') doesn't. But, even if you insisted that it should mean something, it would make far more sense for it to return None than '23'. After all, it's a constructor, it ought to construct an object of its type. But that still wouldn't mean that None should be callable just because NoneType is, any more than it means that 42 should be callable just because int is. Meanwhile, you still haven't answered what "return the arguments" should actually mean, given that in Python, "the arguments" are a tuple of positional arguments plus a dict of keyword arguments. What would be the point in returning that? In a language where a 1-tuple is the same thing as its element, like the ML family or Haskell, and where there are no keyword arguments, something like this might be sensible. But in Python, it's not. Unless you have a way to make sense of it that I haven't thought of--which is why I'm asking.
'' and empty containers come to mind. Just like float and int can take '1', and error if you give it something that can't be converted to a float or int.
The None?(...) syntax looks like it should do a call. Just like it would do if you did int?(...).
Again, int is a type, and None isn't. Callability is a special property that only certain types--functions, methods, types, and things that want to act like one of the above--have. None is not a function, method, or type, any more than 42 is. Second, the whole reason spam?(…) has been proposed in the first place is that None(…) obviously doesn't make sense, so we want a way to avoid evaluating it if spam is None. In a language where everything is callable, there's no reason to even talk about ?().
So both the return None and the return everything are special behaviors. Possibly a None result is a bit more correct.
Making it return None isn't more correct, at least not for Python. It's definitely more correct for a language like SmallTalk with an "active null", which should propagate through almost all expressions by making them evaluate to None rather than raising. In such a language, for None(…) to do anything but return None would be inconsistent. But in a language where None doesn't propagate through any expressions, and instead usually raises a TypeError, for None(…) to return None would be highly inconsistent. And if you're suggesting that we should replace None with a SmallTalk-style active null, that s a much bigger change, which would have to change a lot more than NoneType.__call__, in a way that would radically affect the semantics of millions of APIs, lines of code, etc.
To tell the truth I think looking at some real examples from the library would be good. How can those be improved both in how they read and how they look, and how frequent are they?
People have given real examples (from their own code, not the stdlib) for ?() as defined in the proposal. If you want examples for your different proposal, I think you're the one who needs to give them. (And I agree they would be worthwhile.)
This whole line of discussion started out as a very tenuous suggestion/possibility. I even said it was going out on a limb, so I really wasn't expecting to defend it as if it was a complete idea.
The concept I am thing of is doable, but not in the way I was thinking at first, but I also think it doesn't fit with python very well.
I think either it isn't doable, or it fits into Python even more poorly than you think. But I'm just one guy, who could easily be wrong. If you think otherwise, I would be interested in seeing what you can come up with. Even if it ends up not being a proposal you'd want to back, it would probably give insights to half the people involved in the larger discussion on null coalescing and null conditionals. And if it turns out that I'm right and you can't make it work, convincing yourself of that might lead to useful insights into the larger discussion that you could share here. After all, it seems like we're kind of stuck on conflicting incomplete intuitions here, and just arguing those back and forth isn't getting us very far.
![](https://secure.gravatar.com/avatar/e1f5f984cbc32a8ba5d0f074d2f1cf19.jpg?s=120&d=mm&r=g)
On 10/03/2015 05:59 PM, Andrew Barnert via Python-ideas wrote:
Meanwhile, you still haven't answered what "return the arguments" should actually mean, given that in Python, "the arguments" are a tuple of positional arguments plus a dict of keyword arguments.
The simplest example is if a function has 5 arguments, then it will return those in the order they are in the signature.
def foo(a, b, c, x=1, y=2): ... return a, b, c, x, y
(a, b, c, x, y) = foo(1, 2, 3, 4, 5) a, b, c, x, y (1, 2, 3, 4, 5)
a = foo(1, 2, 3, 4, 5) a (1, 2, 3, 4, 5)
But I think that may not be as useful as I was thinking. Only a few functions return the same number of arguments as they take. It would actually be more applicable to filters, where a function is applied to each item, and None might be the default to do nothing with that item.
This whole line of discussion started out as a very tenuous suggestion/possibility. I even said it was going out on a limb, so I really wasn't expecting to defend it as if it was a complete idea.
The concept I am thing of is doable, but not in the way I was thinking at first, but I also think it doesn't fit with python very well.
I think either it isn't doable, or it fits into Python even more poorly than you think. But I'm just one guy, who could easily be wrong. If you think otherwise, I would be interested in seeing what you can come up with. Even if it ends up not being a proposal you'd want to back, it would probably give insights to half the people involved in the larger discussion on null coalescing and null conditionals. And if it turns out that I'm right and you can't make it work, convincing yourself of that might lead to useful insights into the larger discussion that you could share here. After all, it seems like we're kind of stuck on conflicting incomplete intuitions here, and just arguing those back and forth isn't getting us very far.
That's exactly what we are doing. ;-) I doubt I can do better than what's already suggested at this point. Cheers, Ron
![](https://secure.gravatar.com/avatar/5615a372d9866f203a22b2c437527bbb.jpg?s=120&d=mm&r=g)
On Thu, Oct 01, 2015 at 03:57:18PM -0500, Ron Adam wrote:
On 10/01/2015 02:59 AM, Nick Coghlan wrote:
As a result, one interesting way of looking at this problem is to ask: what if we *only* offered the conditional operations, *without* offering a binary null-coalescing operator?
How languages treat None, null, and nil, are very fundamental aspects of how they work. For example I noticed that if we add an None specific 'or' operator, we may want a None specific 'and' operator too.
Why would we do that? What would it do and which other languages have it?
And in order to use those in conditional statements, it makes sense to have None specific 'if' and 'while'.
Which other languages have these?
So it may be a slippery slope. (Or they may be good to have. I'm very undecided still.)
Most slippery-slope arguments aren't actually that slippery. Sure, we could invent a new short-cut for "if spam is None" statement, but it doesn't buy us much benefit, so why would we bother? if spam.eggs[cheese] is None: ... ifn spam.eggs[cheese]: ... You save a few characters typing, big deal. But with an expression, you can save complexity, by avoiding temporary variables: # before expensive = spam.eggs[cheese] # don't calculate this twice value = expensive.attribute if expensive is not None else None # after value = spam.eggs[cheese]?.attribute [...]
Another possibility is to have a *special magic callable* that when called skips the argument evaluation and returns None.
NoneCall(these, args, are, never, evaluated)
That cannot work with standard Python semantics. In Python, the function doesn't evaluate the arguments, they are evaluated before the function even gets called. For this to work, the parser would have to recognise this NoneCall function *at compile time*, so it can decide whether or not to evaluate the arguments. That is, in general, an impossible task: func = random.choice([NoneCall, lambda *args: None]) func(a+1, b*2, c**3) # to evaluate or not to evaluate? Python has only one thing even remotely like this, the special handling of "super" inside functions, and that's a special case which I think Guido has ruled won't be repeated. But even there, it isn't a change in what gets evaluated when.
Then a special method on objects, obj __cond__call__() can return a NoneCall to skip the arguments on the right side of ?(, or return another callable to use them.
No, it can't work anything like that. It can't be a regular method call, because the arguments are already evaluated before the method call is made. It has to be a change in what code gets compiled. -- Steve
![](https://secure.gravatar.com/avatar/202432bfbf2e81826fe79af29f70cd02.jpg?s=120&d=mm&r=g)
I don't understand the resistance to using ?? ?. ?[] ?() and the quest to contort some English words into this. The concepts "or" and "and" are natural language concepts and "if null" is not. The goal here should be readability and sometimes that readability is enhanced by being slightly different than other programming languages and sometimes it's For example, Java using -> for lambda is confusing to someone coming from C++ where -> means de-reference. C# avoided that confound by using =>. I'm sure someone thought that -> was more Javalike. Very few people writing/reading Python only use Python. There is a strong benefit to being consistent with other languages when there's not "one obvious" way to do it in "more Pythonic" syntax. So far, *none* of the alternatives are easier to read (for my taste) than the ? operators. As to the people that are questioning the need for any of this, I challenge you to look through your projects for code like this: Python: \bif\b.*\bis *(not *)?none *else C-like: ([=!]= *null\b|\bnull *[=!]=) *\? and see how many hits you find. If you want to avoid using None (or null) in your code, that's fine but the reality is that None is used extensively *and* there are *many* cases where we do exactly what these operators simplify. On Wed, Sep 30, 2015 at 6:08 PM, MRAB <python@mrabarnett.plus.com> wrote:
It's only just occurred to me that there's a small inconsistency here. The "?.", "?[" and "?(" will short-circuit on None, whereas the "??" will short-circuit on non-None.
Would that cause any confusion in practice?
Personally, I don't think so and I never thought about this as an area of confusion. In the ?? case, it's obvious Two more notes: (1) If we have ?. ?() ?[] then spelling the ?? operator with a ? makes a lot of sense but mixing a word and an operator (like or?) just looks ugly to me. That said, if Guido declares absolutely no on ?. ?() ?[] and yes on ?? but it has to be spelled with words, my preference would be "*or else*". It doesn't really say "if is None" to me but (i) it's a syntax error right now; and (ii) the else in there implies some if-like logic is going on rather than just or-like logic: (foo or else bar) == (foo if foo is not None else bar). (2) As to ?= or ??=, I am less enamored of that but not because that case never occurs. I often find that the initialization code after if foo is null: is more than a single expression and therefore not amenable to ?=. That is, I don't just want to create an object and assign to the variable, I want to make initialization calls to it. --- Bruce Check out my new puzzle book: http://J.mp/ingToConclusions <http://j.mp/ingToConclusions> Get it free here: http://J.mp/ingToConclusionsFree <http://j.mp/ingToConclusionsFree> (available on iOS)
![](https://secure.gravatar.com/avatar/130fe9f08ce5d2b1716d32438a58c867.jpg?s=120&d=mm&r=g)
On 01.10.2015 04:53, Bruce Leban wrote:
If you want to avoid using None (or null) in your code, that's fine but the reality is that None is used extensively *and* there are *many* cases where we do exactly what these operators simplify.
Nothing what you said is wrong. However, it lacks the deeper understanding of the None issue. Just because everybody tells you the world is flat doesn't make it flat. Tell others it's round instead. Best, Sven
![](https://secure.gravatar.com/avatar/5615a372d9866f203a22b2c437527bbb.jpg?s=120&d=mm&r=g)
On Mon, Sep 28, 2015 at 01:54:09PM -0700, Guido van Rossum wrote:
If you want to dumb down the feature so that foo?.bar.baz means just (foo?.bar).baz then it's useless and I should just reject the PEP.
In case anyone missed it, according to the PEP author Mark Haase, that's the behaviour of Dart, and it is useless: "Your interpretation of Dart's semantics is correct, and I agree that's absolutely the wrong way to do it. C# does have the short-circuit semantics that you're looking for." https://mail.python.org/pipermail/python-ideas/2015-September/036495.html -- Steve
![](https://secure.gravatar.com/avatar/ebf132362b622423ed5baca2988911b8.jpg?s=120&d=mm&r=g)
On September 28, 2015 at 4:25:12 PM, Guido van Rossum (guido@python.org) wrote:
On Mon, Sep 28, 2015 at 1:15 PM, Donald Stufft wrote:
The ? Modifying additional attribute accesses beyond just the immediate one bothers me too and feels more ruby than python to me.
Really? Have you thought about it?
Not extensively, mostly this is a gut feeling.
Suppose I have an object post which may be None or something with a tag attribute which should be a string. And suppose I want to get the lowercased tag, if the object exists, else None.
This seems a perfect use case for writing post?.tag.lower() -- this signifies that post may be None but if it exists, post.tag is not expected to be None. So basically I want the equivalent of (post.tag.lower() if post is not None else None).
But if post?.tag.lower() were interpreted strictly as (post?.tag).lower(), then I would have to write post?.tag?.lower?(), which is an abomination. OTOH if post?.tag.lower() automatically meant post?.tag?.lower?() then I would silently get no error when post exists but post.tag is None (which in this example is an error).
Does ? propagate past a non None value? If it were post?.tag.name.lower() and post was not None, but tag was None would that be an error or would the ? propagate to the tag as well? ----------------- Donald Stufft PGP: 0x6E3CBCE93372DCFA // 7C6B 7C5D 5E2B 6356 A926 F04F 6E3C BCE9 3372 DCFA
![](https://secure.gravatar.com/avatar/047f2332cde3730f1ed661eebb0c5686.jpg?s=120&d=mm&r=g)
On Mon, Sep 28, 2015 at 1:41 PM, Donald Stufft <donald@stufft.io> wrote:
On September 28, 2015 at 4:25:12 PM, Guido van Rossum (guido@python.org) wrote:
On Mon, Sep 28, 2015 at 1:15 PM, Donald Stufft wrote:
The ? Modifying additional attribute accesses beyond just the immediate one bothers me too and feels more ruby than python to me.
Really? Have you thought about it?
Not extensively, mostly this is a gut feeling.
Suppose I have an object post which may be None or something with a tag attribute which should be a string. And suppose I want to get the lowercased tag, if the object exists, else None.
This seems a perfect use case for writing post?.tag.lower() -- this signifies that post may be None but if it exists, post.tag is not
to be None. So basically I want the equivalent of (post.tag.lower() if
expected post
is not None else None).
But if post?.tag.lower() were interpreted strictly as (post?.tag).lower(), then I would have to write post?.tag?.lower?(), which is an abomination. OTOH if post?.tag.lower() automatically meant post?.tag?.lower?() then I would silently get no error when post exists but post.tag is None (which in this example is an error).
Does ? propagate past a non None value? If it were post?.tag.name.lower() and post was not None, but tag was None would that be an error or would the ? propagate to the tag as well?
I was trying to clarify that by saying that foo?.bar.baz means (foo.bar.baz if foo is not None else None). IOW if tag was None that would be an error. The rule then is quite simple: each ? does exactly one None check and divides the expression into exactly two branches -- one for the case where the thing preceding ? is None and one for the case where it isn't. -- --Guido van Rossum (python.org/~guido)
![](https://secure.gravatar.com/avatar/202432bfbf2e81826fe79af29f70cd02.jpg?s=120&d=mm&r=g)
On Mon, Sep 28, 2015 at 1:56 PM, Guido van Rossum <guido@python.org> wrote:
The rule then is quite simple: each ? does exactly one None check and divides the expression into exactly two branches -- one for the case where the thing preceding ? is None and one for the case where it isn't.
I think this is exactly the right rule (when combined with the previously stated rule that ?. ?() ?[] have the same precedence as the standard versions of those operators). --- Bruce Check out my new puzzle book: http://J.mp/ingToConclusions Get it free here: http://J.mp/ingToConclusionsFree (available on iOS)
![](https://secure.gravatar.com/avatar/ebf132362b622423ed5baca2988911b8.jpg?s=120&d=mm&r=g)
On September 28, 2015 at 4:56:44 PM, Guido van Rossum (guido@python.org) wrote:
On Mon, Sep 28, 2015 at 1:41 PM, Donald Stufft wrote:
On September 28, 2015 at 4:25:12 PM, Guido van Rossum (guido@python.org) wrote:
On Mon, Sep 28, 2015 at 1:15 PM, Donald Stufft wrote:
The ? Modifying additional attribute accesses beyond just the immediate one bothers me too and feels more ruby than python to me.
Really? Have you thought about it?
Not extensively, mostly this is a gut feeling.
Suppose I have an object post which may be None or something with a tag attribute which should be a string. And suppose I want to get the lowercased tag, if the object exists, else None.
This seems a perfect use case for writing post?.tag.lower() -- this signifies that post may be None but if it exists, post.tag is not
to be None. So basically I want the equivalent of (post.tag.lower() if
expected post
is not None else None).
But if post?.tag.lower() were interpreted strictly as (post?.tag).lower(), then I would have to write post?.tag?.lower?(), which is an abomination. OTOH if post?.tag.lower() automatically meant post?.tag?.lower?() then I would silently get no error when post exists but post.tag is None (which in this example is an error).
Does ? propagate past a non None value? If it were post?.tag.name.lower() and post was not None, but tag was None would that be an error or would the ? propagate to the tag as well?
I was trying to clarify that by saying that foo?.bar.baz means (foo.bar.baz if foo is not None else None). IOW if tag was None that would be an error.
The rule then is quite simple: each ? does exactly one None check and divides the expression into exactly two branches -- one for the case where the thing preceding ? is None and one for the case where it isn't.
Ok, that makes me feel less bad than my initial impression was that ? was going to modify all following things so that they were all implicitly ?. Just splitting it into two different branches seems OK. I’m not a big fan of the punctuation though. It took me a minute to realize that post?.tag.lower() was saying if post is None, not if post.tag is None and I feel like it’s easy to miss the ?, especially when combined with other punctuation. ----------------- Donald Stufft PGP: 0x6E3CBCE93372DCFA // 7C6B 7C5D 5E2B 6356 A926 F04F 6E3C BCE9 3372 DCFA
![](https://secure.gravatar.com/avatar/047f2332cde3730f1ed661eebb0c5686.jpg?s=120&d=mm&r=g)
On Mon, Sep 28, 2015 at 2:06 PM, Donald Stufft <donald@stufft.io> wrote:
I’m not a big fan of the punctuation though. It took me a minute to realize that post?.tag.lower() was saying if post is None, not if post.tag is None and I feel like it’s easy to miss the ?, especially when combined with other punctuation.
But that's a different point (for the record I'm not a big fan of the ? either). -- --Guido van Rossum (python.org/~guido)
![](https://secure.gravatar.com/avatar/0a2191a85455df6d2efdb22c7463c304.jpg?s=120&d=mm&r=g)
On 28.09.2015 23:49, Guido van Rossum wrote:
On Mon, Sep 28, 2015 at 2:06 PM, Donald Stufft <donald@stufft.io> wrote:
I’m not a big fan of the punctuation though. It took me a minute to realize that post?.tag.lower() was saying if post is None, not if post.tag is None and I feel like it’s easy to miss the ?, especially when combined with other punctuation.
But that's a different point (for the record I'm not a big fan of the ? either).
Me neither. The proposal simply doesn't have the right balance between usefulness and complexity added to the language (esp. for new Python programmers to learn in order to be able to read a Python program). In practice, you can very often write "x or y" instead of having to use "x if x is None else y", simply because you're not only interested in catching the x is None case, but also want to override an empty string or sequence value with a default. If you really need to specifically check for None, "x if x is None else y" is way more expressive than "x ?? y". For default parameters with mutable types as values, I usually write: def func(x=None): if x is None: x = [] ... IMO, that's better than any of the above, but perhaps that's just because I don't believe in the "write everything in a single line" pattern as something we should strive for in Python. The other variants (member and index access) look like typos to me ;-) -- Marc-Andre Lemburg eGenix.com Professional Python Services directly from the Experts (#1, Sep 29 2015)
Python Projects, Coaching and Consulting ... http://www.egenix.com/ Python Database Interfaces ... http://products.egenix.com/ Plone/Zope Database Interfaces ... http://zope.egenix.com/
2015-09-25: Started a Python blog ... ... http://malemburg.com/ 2015-10-21: Python Meeting Duesseldorf ... 22 days to go ::::: Try our mxODBC.Connect Python Database Interface for free ! :::::: eGenix.com Software, Skills and Services GmbH Pastor-Loeh-Str.48 D-40764 Langenfeld, Germany. CEO Dipl.-Math. Marc-Andre Lemburg Registered at Amtsgericht Duesseldorf: HRB 46611 http://www.egenix.com/company/contact/
![](https://secure.gravatar.com/avatar/dd4761743695d5efd3692f2a3b35d37d.jpg?s=120&d=mm&r=g)
On Tue, Sep 29, 2015 at 3:49 AM, M.-A. Lemburg <mal@egenix.com> wrote:
On 28.09.2015 23:49, Guido van Rossum wrote:
But that's a different point (for the record I'm not a big fan of the ? either).
Me neither.
Same here.
The proposal simply doesn't have the right balance between usefulness and complexity added to the language (esp. for new Python programmers to learn in order to be able to read a Python program).
+1
In practice, you can very often write "x or y" instead of having to use "x if x is None else y", simply because you're not only interested in catching the x is None case, but also want to override an empty string or sequence value with a default. If you really need to specifically check for None, "x if x is None else y" is way more expressive than "x ?? y".
For default parameters with mutable types as values, I usually write:
def func(x=None): if x is None: x = [] ...
I do the same. It has the right amount of explicitness and makes the default-case branch more obvious (subjectively, of course) than the proposed alternative: def func(x=None): x = x ?? [] ...
IMO, that's better than any of the above, but perhaps that's just because I don't believe in the "write everything in a single line" pattern as something we should strive for in Python.
Yeah, the language has been pretty successful at striking the right balance here. IMO, the proposed syntax doesn't pay off. -eric
![](https://secure.gravatar.com/avatar/d995b462a98fea412efa79d17ba3787a.jpg?s=120&d=mm&r=g)
On 29 September 2015 at 14:40, Eric Snow <ericsnowcurrently@gmail.com> wrote:
For default parameters with mutable types as values, I usually write:
def func(x=None): if x is None: x = [] ...
I do the same. It has the right amount of explicitness and makes the default-case branch more obvious (subjectively, of course) than the proposed alternative:
def func(x=None): x = x ?? []
Looking at those two cases in close proximity like that, I have to say that the explicit if statement wins hands down. But it's not quite as obvious with multiple arguments where the target isn't the same as the parameter (for example with a constructor): def __init__(self, vertices=None, edges=None, weights=None, source_nodes=None): if vertices is None: self.vertices = [] else: self.vertices = vertices if edges is None: self.edges = [] else: self.edges = edges if weights is None: self.weights = {} else: self.weights = weights if source_nodes is None: self.source_nodes = [] else: self.source_nodes = source_nodes vs def __init__(self, vertices=None, edges=None, weights=None, source_nodes=None): self.vertices = vertices or? [] self.edges = edges or? [] self.weights = weights or? {} self.source_nodes = source_nodes or? [] Having said all of that, short circuiting is not important here, so def default(var, dflt): if var is None: return dflt return var def __init__(self, vertices=None, edges=None, weights=None, source_nodes=None): self.vertices = default(vertices, []) self.edges = default(edges, []) self.weights = default(weights, {}) self.source_nodes = default(source_nodes, []) is also an option. In this case, my preference is probably (1) a default() function, (2) or?, (3) multi-line if. The default() function approach can be used for cases where the condition is something *other* than "is None" so that one edges ahead of or? because it's more flexible... (although (1) wouldn't be an option if short-circuiting really mattered...) In practice, of course, I never write a default() function at the moment, I just use multi-line ifs. Whether that means I'd use an or? operator, I don't know. Probably - but I'd likely consider it a bit of a "too many ways of doing the same thing" wart at the same time... Paul
![](https://secure.gravatar.com/avatar/880ac02012dcaf99f0392f69af4f8597.jpg?s=120&d=mm&r=g)
On 29/09/2015 15:56, Paul Moore wrote:
For default parameters with mutable types as values, I usually write:
def func(x=None): if x is None: x = [] ... I do the same. It has the right amount of explicitness and makes the default-case branch more obvious (subjectively, of course) than the proposed alternative:
def func(x=None): x = x ?? [] Looking at those two cases in close proximity like that, I have to say
On 29 September 2015 at 14:40, Eric Snow <ericsnowcurrently@gmail.com> wrote: that the explicit if statement wins hands down.
But it's not quite as obvious with multiple arguments where the target isn't the same as the parameter (for example with a constructor):
def __init__(self, vertices=None, edges=None, weights=None, source_nodes=None): if vertices is None: self.vertices = [] else: self.vertices = vertices if edges is None: self.edges = [] else: self.edges = edges if weights is None: self.weights = {} else: self.weights = weights if source_nodes is None: self.source_nodes = [] else: self.source_nodes = source_nodes vs
def __init__(self, vertices=None, edges=None, weights=None, source_nodes=None): self.vertices = vertices or? [] self.edges = edges or? [] self.weights = weights or? {} self.source_nodes = source_nodes or? []
Having said all of that, short circuiting is not important here, so
def default(var, dflt): if var is None: return dflt return var
def __init__(self, vertices=None, edges=None, weights=None, source_nodes=None): self.vertices = default(vertices, []) self.edges = default(edges, []) self.weights = default(weights, {}) self.source_nodes = default(source_nodes, [])
is also an option.
Why not def __init__(self, vertices=None, edges=None, weights=None, source_nodes=None): self.vertices = vertices if vertices is not None else [] self.edges = edges if edges is not None else [] self.weights = weights if weights is not None else {} self.source_nodes = source_nodes if source_nodes is not None else [] Completely explicit. Self-contained (you don't need to look up a helper function). Reasonably compact (at least vertically). Easy to make a change to one of the lines if the logic of that line changes. Doesn't need a language change. And if you align the lines (as I have attempted to, although different proportional fonts may make it look ragged), it highlights the common structure of the lines *and* their differences (you can see that one line has "{}" instead of "[]" because it stands out). Rob Cliffe
![](https://secure.gravatar.com/avatar/4655830e7145e4fbe09aca7a38589721.jpg?s=120&d=mm&r=g)
On 9/29/2015 9:20 AM, Rob Cliffe wrote:
Why not
def __init__(self, vertices=None, edges=None, weights=None, source_nodes=None): self.vertices = vertices if vertices is not None else [] self.edges = edges if edges is not None else [] self.weights = weights if weights is not None else {} self.source_nodes = source_nodes if source_nodes is not None else []
I don't understand why not: self.vertices = vertices or [] self.edges = edges or [] self.weights = weights or {} self.source_nodes = source_nodes or [] Emile
![](https://secure.gravatar.com/avatar/130fe9f08ce5d2b1716d32438a58c867.jpg?s=120&d=mm&r=g)
On 29.09.2015 18:58, Emile van Sebille wrote:
On 9/29/2015 9:20 AM, Rob Cliffe wrote:
Why not
def __init__(self, vertices=None, edges=None, weights=None, source_nodes=None): self.vertices = vertices if vertices is not None else [] self.edges = edges if edges is not None else [] self.weights = weights if weights is not None else {} self.source_nodes = source_nodes if source_nodes is not None else []
I don't understand why not:
self.vertices = vertices or [] self.edges = edges or [] self.weights = weights or {} self.source_nodes = source_nodes or []
People fear that when you pass some special objects into the constructor that behaves like False, this special object is replaced by [] or {}. I for one don't think it's a real issue. However, it has been said that people got bitten by this in the past. I don't know what the heck they did, but I presume they tried something really really nasty. Best, Sven
![](https://secure.gravatar.com/avatar/7e41acaa8f6a0e0f5a7c645e93add55a.jpg?s=120&d=mm&r=g)
On Sep 29, 2015, at 09:58, Emile van Sebille <emile@fenx.com> wrote:
On 9/29/2015 9:20 AM, Rob Cliffe wrote: Why not
def __init__(self, vertices=None, edges=None, weights=None, source_nodes=None): self.vertices = vertices if vertices is not None else [] self.edges = edges if edges is not None else [] self.weights = weights if weights is not None else {} self.source_nodes = source_nodes if source_nodes is not None else []
I don't understand why not:
self.vertices = vertices or [] self.edges = edges or [] self.weights = weights or {} self.source_nodes = source_nodes or []
Because empty containers are just as falsey as None. So, if I pass in a shared list, your "vertices or []" will replace it with a new, unshared list; if I pass a tuple because I need an immutable graph, you'll replace it with a mutable list; if I pass in a blist.sortedlist, you'll replace it with a plain list. Worse, this will only happen if the argument I pass happens to be empty, which I may not have thought to test for. This is the same reason you don't use "if spam:" when you meant "if spam is not None:", which is explained in PEP 8. Also, I believe the PEP for ternary if-else explains why this is an "attractive nuisance" misuse of or, as one of the major arguments for why a ternary expression should be added.
![](https://secure.gravatar.com/avatar/4655830e7145e4fbe09aca7a38589721.jpg?s=120&d=mm&r=g)
Thanks -- I think I've got a better handle now on the why of this discussion. Emile On 9/29/2015 1:33 PM, Andrew Barnert via Python-ideas wrote:
On Sep 29, 2015, at 09:58, Emile van Sebille <emile@fenx.com> wrote:
On 9/29/2015 9:20 AM, Rob Cliffe wrote: Why not
def __init__(self, vertices=None, edges=None, weights=None, source_nodes=None): self.vertices = vertices if vertices is not None else [] self.edges = edges if edges is not None else [] self.weights = weights if weights is not None else {} self.source_nodes = source_nodes if source_nodes is not None else []
I don't understand why not:
self.vertices = vertices or [] self.edges = edges or [] self.weights = weights or {} self.source_nodes = source_nodes or []
Because empty containers are just as falsey as None.
So, if I pass in a shared list, your "vertices or []" will replace it with a new, unshared list; if I pass a tuple because I need an immutable graph, you'll replace it with a mutable list; if I pass in a blist.sortedlist, you'll replace it with a plain list. Worse, this will only happen if the argument I pass happens to be empty, which I may not have thought to test for.
This is the same reason you don't use "if spam:" when you meant "if spam is not None:", which is explained in PEP 8.
Also, I believe the PEP for ternary if-else explains why this is an "attractive nuisance" misuse of or, as one of the major arguments for why a ternary expression should be added. _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
![](https://secure.gravatar.com/avatar/dd4761743695d5efd3692f2a3b35d37d.jpg?s=120&d=mm&r=g)
On Tue, Sep 29, 2015 at 8:56 AM, Paul Moore <p.f.moore@gmail.com> wrote:
But it's not quite as obvious with multiple arguments where the target isn't the same as the parameter (for example with a constructor):
def __init__(self, vertices=None, edges=None, weights=None, source_nodes=None): if vertices is None: self.vertices = [] else: self.vertices = vertices if edges is None: self.edges = [] else: self.edges = edges if weights is None: self.weights = {} else: self.weights = weights if source_nodes is None: self.source_nodes = [] else: self.source_nodes = source_nodes
Personally I usually keep the defaults handling separate, like so: def __init__(self, vertices=None, edges=None, weights=None, source_nodes=None): if vertices is None: vertices = [] if edges is None: edges = [] if weights is None: weights = {} if source_nodes is None: source_nodes = [] self.vertices = vertices self.edges = edges self.weights = weights self.source_nodes = source_nodes ...and given the alternatives presented here, I'd likely continue doing so. To me the others are less distinct about how defaults are set and invite more churn if you have to do anything extra down the road when composing a default. Regardless, YMMV. [snip]
In practice, of course, I never write a default() function at the moment, I just use multi-line ifs. Whether that means I'd use an or? operator, I don't know. Probably - but I'd likely consider it a bit of a "too many ways of doing the same thing" wart at the same time...
Right. And it doesn't really pay for itself when measured against that cost. -eric
![](https://secure.gravatar.com/avatar/d995b462a98fea412efa79d17ba3787a.jpg?s=120&d=mm&r=g)
On 29 September 2015 at 18:15, Eric Snow <ericsnowcurrently@gmail.com> wrote:
Personally I usually keep the defaults handling separate, like so: [...] ...and given the alternatives presented here, I'd likely continue doing so. To me the others are less distinct about how defaults are set and invite more churn if you have to do anything extra down the road when composing a default. Regardless, YMMV.
Agreed, there's many ways, and the new operator doesn't really add a huge amount (other than yet another way of doing things).
[snip]
In practice, of course, I never write a default() function at the moment, I just use multi-line ifs. Whether that means I'd use an or? operator, I don't know. Probably - but I'd likely consider it a bit of a "too many ways of doing the same thing" wart at the same time...
Right. And it doesn't really pay for itself when measured against that cost.
Precisely. Paul
![](https://secure.gravatar.com/avatar/2fc5b058e338d06a8d8f8cd0cfe48376.jpg?s=120&d=mm&r=g)
On 09/29/2015 03:40 PM, Eric Snow wrote:
On Tue, Sep 29, 2015 at 3:49 AM, M.-A. Lemburg <mal@egenix.com> wrote:
On 28.09.2015 23:49, Guido van Rossum wrote:
But that's a different point (for the record I'm not a big fan of the ? either).
Me neither.
Same here.
The proposal simply doesn't have the right balance between usefulness and complexity added to the language (esp. for new Python programmers to learn in order to be able to read a Python program).
+1
I agree as well.
In practice, you can very often write "x or y" instead of having to use "x if x is None else y", simply because you're not only interested in catching the x is None case, but also want to override an empty string or sequence value with a default. If you really need to specifically check for None, "x if x is None else y" is way more expressive than "x ?? y".
For default parameters with mutable types as values, I usually write:
def func(x=None): if x is None: x = [] ...
I do the same. It has the right amount of explicitness and makes the default-case branch more obvious (subjectively, of course) than the proposed alternative:
def func(x=None): x = x ?? []
Looking at this, I think people might call ?? the "WTF operator". Not a good sign :) Georg
![](https://secure.gravatar.com/avatar/5615a372d9866f203a22b2c437527bbb.jpg?s=120&d=mm&r=g)
On Tue, Sep 29, 2015 at 06:37:46PM +0200, Georg Brandl wrote:
x = x ?? []
Looking at this, I think people might call ?? the "WTF operator". Not a good sign :)
I see your smiley, but C# has this operator. What do C# programmers call it? ("Null coalescing operator" is the formal name, but that's way too long for everyday use.) -- Steve
![](https://secure.gravatar.com/avatar/ad24799da38da64c3b51b745087d60fd.jpg?s=120&d=mm&r=g)
On Tue, Sep 29, 2015 at 6:02 PM, Steven D'Aprano <steve@pearwood.info> wrote:
On Tue, Sep 29, 2015 at 06:37:46PM +0200, Georg Brandl wrote:
x = x ?? []
Looking at this, I think people might call ?? the "WTF operator". Not a good sign :)
I see your smiley, but C# has this operator. What do C# programmers call it? ("Null coalescing operator" is the formal name, but that's way too long for everyday use.)
I've never seen it referred to as anything other than "the null coalescing operator" (or occasionally the "double question mark operator"). C# devs aren't necessarily the most creative bunch... :) - Jeff
![](https://secure.gravatar.com/avatar/ad24799da38da64c3b51b745087d60fd.jpg?s=120&d=mm&r=g)
On Mon, Sep 28, 2015 at 1:56 PM, Guido van Rossum <guido@python.org> wrote:
On Mon, Sep 28, 2015 at 1:41 PM, Donald Stufft <donald@stufft.io> wrote:
On September 28, 2015 at 4:25:12 PM, Guido van Rossum (guido@python.org) wrote:
On Mon, Sep 28, 2015 at 1:15 PM, Donald Stufft wrote:
The ? Modifying additional attribute accesses beyond just the immediate one bothers me too and feels more ruby than python to me.
Really? Have you thought about it?
Not extensively, mostly this is a gut feeling.
Suppose I have an object post which may be None or something with a tag attribute which should be a string. And suppose I want to get the lowercased tag, if the object exists, else None.
This seems a perfect use case for writing post?.tag.lower() -- this signifies that post may be None but if it exists, post.tag is not
to be None. So basically I want the equivalent of (post.tag.lower() if
expected post
is not None else None).
But if post?.tag.lower() were interpreted strictly as (post?.tag).lower(), then I would have to write post?.tag?.lower?(), which is an abomination. OTOH if post?.tag.lower() automatically meant post?.tag?.lower?() then I would silently get no error when post exists but post.tag is None (which in this example is an error).
Does ? propagate past a non None value? If it were post?.tag.name.lower() and post was not None, but tag was None would that be an error or would the ? propagate to the tag as well?
I was trying to clarify that by saying that foo?.bar.baz means (foo.bar.baz if foo is not None else None). IOW if tag was None that would be an error.
The rule then is quite simple: each ? does exactly one None check and divides the expression into exactly two branches -- one for the case where the thing preceding ? is None and one for the case where it isn't.
This whole line of discussion is why I'd prefer the PEP be split to have ?? in one and ?., ?[, etc. in another (the thread I linked isn't even the longest one discussing the associativity - there were many that preceded it). I agree that the short circuit behaviour is the only one that makes any sense, but I also don't want to see the very useful ?? operator lost because of discussions over or implementation difficulties of ?. or ?[. And if it's going to be done anyway, I'd to see ?( as well. - Jeff
![](https://secure.gravatar.com/avatar/e572da4355c07804e3300bf879ffbe64.jpg?s=120&d=mm&r=g)
Carl Meyer <carl@oddbird.net> writes:
On 09/28/2015 01:53 PM, Guido van Rossum wrote:
On Mon, Sep 28, 2015 at 12:43 PM, Carl Meyer:
I'm having trouble coming up with a parallel example where the existing short-circuit operators break "extractibility" of a sub-expression like that.
Why is that an interesting property?
Because breaking up an overly-complex expression into smaller expressions by means of extracting sub-expressions into temporary variables is a common programming task
+1, this is a hugely important tool in the mental toolkit. Making that more difficult is a high cost, thank you for expressing it so explicitly.
it's usually one that can be handled pretty mechanically according to precedence rules, without having to consider that some operators might have action-at-a-distance beyond their precedence.
I don't know, but I think you shouldn't worry about this.
I think it's kind of odd, but if nobody else is worried about it, I won't worry about it either :-)
I share the concerns Carl is expressing; action-at-a-distance is something I'm glad Python doesn't have much of, and I would be loath to see that increase. -- \ “A lie can be told in a few words. Debunking that lie can take | `\ pages. That is why my book… is five hundred pages long.” —Chris | _o__) Rodda, 2011-05-05 | Ben Finney
![](https://secure.gravatar.com/avatar/047f2332cde3730f1ed661eebb0c5686.jpg?s=120&d=mm&r=g)
On Mon, Sep 28, 2015 at 1:27 PM, Ben Finney <ben+python@benfinney.id.au> wrote:
Carl Meyer <carl@oddbird.net> writes:
On 09/28/2015 01:53 PM, Guido van Rossum wrote:
On Mon, Sep 28, 2015 at 12:43 PM, Carl Meyer:
I'm having trouble coming up with a parallel example where the existing short-circuit operators break "extractibility" of a sub-expression like that.
Why is that an interesting property?
Because breaking up an overly-complex expression into smaller expressions by means of extracting sub-expressions into temporary variables is a common programming task
+1, this is a hugely important tool in the mental toolkit. Making that more difficult is a high cost, thank you for expressing it so explicitly.
it's usually one that can be handled pretty mechanically according to precedence rules, without having to consider that some operators might have action-at-a-distance beyond their precedence.
I don't know, but I think you shouldn't worry about this.
I think it's kind of odd, but if nobody else is worried about it, I won't worry about it either :-)
I share the concerns Carl is expressing; action-at-a-distance is something I'm glad Python doesn't have much of, and I would be loath to see that increase.
Really? You would consider a syntactic feature whose scope is limited to things to its immediate right with the most tightly binding pseudo-operators "action-at-a-distance"? The rhetoric around this issue is beginning to sound ridiculous. -- --Guido van Rossum (python.org/~guido)
![](https://secure.gravatar.com/avatar/7e41acaa8f6a0e0f5a7c645e93add55a.jpg?s=120&d=mm&r=g)
On Monday, September 28, 2015 12:05 PM, Guido van Rossum <guido@python.org> wrote:
On Mon, Sep 28, 2015 at 10:38 AM, Carl Meyer <carl@oddbird.net> wrote:
"Propagating" refers to the proposed behavior where use of ?. or ?[ "propagates" through the following chain of operations. For example:
x = foo?.bar.spam.eggs
Where both `.spam` and `.eggs` would behave like `?.spam` and `?.eggs` (propagating None rather than raising AttributeError), simply because a `.?` had occurred earlier in the chain. So the above behaves differently from:
temp = foo?.bar x = temp.spam.eggs
Which raises questions about whether the propagation escapes parentheses, too:
x = (foo?.bar).spam.eggs
Oh, I see. That's evil.
The correct behavior here is that "foo?.bar.spam.eggs" should mean the same as
(None if foo is None else foo.bar.spam.eggs)
(Stop until you understand that is *not* the same as either of the alternatives you describe.)
I can see the confusion that led to the idea of "propagation" -- it probably comes from an attempt to define "foo?.bar" without reference to the context (in this case the relevant context is that it's followed by ".spam.eggs").
It would really help to have a complete spec, or at least a quick workthrough of how an expression gets parsed and compiled. I assume it's something like this: spam?.eggs.cheese becomes this pseudo-AST (I've skipped the loads and maybe some other stuff): Expr( value=Attribute( value=Attribute( value=Name(id='spam'), attr='eggs', uptalk=True), attr='cheese', uptalk=False)) … which is then compiled as this pseudo-bytecode: LOAD_NAME 'spam' DUP_TOP POP_JUMP_IF_NONE :label LOAD_ATTR 'eggs' LOAD_ATTR 'cheese' :label I've invented a new opcode POP_JUMP_IF_NONE, but it should be clear what it does. I think it's clear how replacing spam with any other expression works, and how subscripting works. So the only question is whether understanding how .eggs.cheese becomes a pair of LOAD_ATTRs is sufficient to understand how ?.eggs.cheese becomes a JUMP_IF_NONE followed by the same pair of LOAD_ATTRs through the same two steps. I suppose the reference documentation wording is also important here, to explain that an uptalked attributeref or subscription short-circuits the whole primary.
![](https://secure.gravatar.com/avatar/047f2332cde3730f1ed661eebb0c5686.jpg?s=120&d=mm&r=g)
On Mon, Sep 28, 2015 at 12:47 PM, Andrew Barnert <abarnert@yahoo.com> wrote:
On Mon, Sep 28, 2015 at 10:38 AM, Carl Meyer <carl@oddbird.net> wrote:
"Propagating" refers to the proposed behavior where use of ?. or ?[ "propagates" through the following chain of operations. For example:
x = foo?.bar.spam.eggs
Where both `.spam` and `.eggs` would behave like `?.spam` and `?.eggs` (propagating None rather than raising AttributeError), simply because a `.?` had occurred earlier in the chain. So the above behaves differently from:
temp = foo?.bar x = temp.spam.eggs
Which raises questions about whether the propagation escapes parentheses, too:
x = (foo?.bar).spam.eggs
Oh, I see. That's evil.
The correct behavior here is that "foo?.bar.spam.eggs" should mean the same as
(None if foo is None else foo.bar.spam.eggs)
(Stop until you understand that is *not* the same as either of the alternatives you describe.)
I can see the confusion that led to the idea of "propagation" -- it
On Monday, September 28, 2015 12:05 PM, Guido van Rossum <guido@python.org> wrote: probably comes from an attempt to define "foo?.bar" without reference to the context (in this case the relevant context is that it's followed by ".spam.eggs").
It would really help to have a complete spec, or at least a quick workthrough of how an expression gets parsed and compiled.
Isn't the PEP author still planning to do that? But it hasn't happened yet. :-(
I assume it's something like this:
spam?.eggs.cheese becomes this pseudo-AST (I've skipped the loads and maybe some other stuff):
Expr( value=Attribute( value=Attribute( value=Name(id='spam'), attr='eggs', uptalk=True), attr='cheese', uptalk=False))
Hm, I think the problem is that this way of representing the tree encourages thinking that each attribute (with or without ?) can be treated on its own. … which is then compiled as this pseudo-bytecode:
LOAD_NAME 'spam' DUP_TOP POP_JUMP_IF_NONE :label LOAD_ATTR 'eggs' LOAD_ATTR 'cheese' :label
I've invented a new opcode POP_JUMP_IF_NONE, but it should be clear what it does. I think it's clear how replacing spam with any other expression works, and how subscripting works. So the only question is whether understanding how .eggs.cheese becomes a pair of LOAD_ATTRs is sufficient to understand how ?.eggs.cheese becomes a JUMP_IF_NONE followed by the same pair of LOAD_ATTRs through the same two steps.
To most people of course that's indecipherable mumbo-jumbo. :-)
I suppose the reference documentation wording is also important here, to explain that an uptalked attributeref or subscription short-circuits the whole primary.
Apparently clarifying that is the entire point of this thread. :-) -- --Guido van Rossum (python.org/~guido)
![](https://secure.gravatar.com/avatar/3dd475b8aaa5292d74cb0c3f76c3f196.jpg?s=120&d=mm&r=g)
On Mon, Sep 28, 2015, at 17:48, Guido van Rossum wrote:
Expr( value=Attribute( value=Attribute( value=Name(id='spam'), attr='eggs', uptalk=True), attr='cheese', uptalk=False))
Hm, I think the problem is that this way of representing the tree encourages thinking that each attribute (with or without ?) can be treated on its own.
How else would you represent it? Maybe some sort of expression that represents a _list_ of attribute/item/call "operators" that are each applied, and if one of them results in none and has uptalk=True it can yield early. Something like... AtomExpr(atom=Name('spam'), trailers=[Attribute('eggs', uptalk=True), Attribute('cheese', uptalk=False)]) For a more complex example: a?.b.c?[12](34).f(56)?(78) AtomExpr(Name('a'), [ Attribute('b', True), Attribute('c', False), Subscript(12, True), Call([34], False), Attribute('f', False), Call([56], False), Call([78], True)]) I almost sent this with it called "Thing", but I checked the grammar and found an element this thing actually maps to.
![](https://secure.gravatar.com/avatar/047f2332cde3730f1ed661eebb0c5686.jpg?s=120&d=mm&r=g)
I would at least define different classes for the uptalk versions. But my main complaint is using the parse tree as a spec at all -- it has way too much noise for a clear description. We don't describe a < b < c by first translating it to (Comparison(a, Comparison(b, c, chained=False), chained=True) either: the reference manual uses a postfix * (i.e. repetition) operator to describe chained comparisons -- while for other operators it favors a recursive definition. On Mon, Sep 28, 2015 at 7:22 PM, Random832 <random832@fastmail.com> wrote:
On Mon, Sep 28, 2015, at 17:48, Guido van Rossum wrote:
Expr( value=Attribute( value=Attribute( value=Name(id='spam'), attr='eggs', uptalk=True), attr='cheese', uptalk=False))
Hm, I think the problem is that this way of representing the tree encourages thinking that each attribute (with or without ?) can be treated on its own.
How else would you represent it? Maybe some sort of expression that represents a _list_ of attribute/item/call "operators" that are each applied, and if one of them results in none and has uptalk=True it can yield early.
Something like...
AtomExpr(atom=Name('spam'), trailers=[Attribute('eggs', uptalk=True), Attribute('cheese', uptalk=False)])
For a more complex example:
a?.b.c?[12](34).f(56)?(78)
AtomExpr(Name('a'), [ Attribute('b', True), Attribute('c', False), Subscript(12, True), Call([34], False), Attribute('f', False), Call([56], False), Call([78], True)])
I almost sent this with it called "Thing", but I checked the grammar and found an element this thing actually maps to. _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
-- --Guido van Rossum (python.org/~guido)
![](https://secure.gravatar.com/avatar/3dd475b8aaa5292d74cb0c3f76c3f196.jpg?s=120&d=mm&r=g)
Guido van Rossum <guido@python.org> writes:
I would at least define different classes for the uptalk versions.
But my main complaint is using the parse tree as a spec at all
Like I said, I actually came up with that structure *before* seeing that it mirrored a grammar element - it honestly seems like the most natural way to embody the fact that evaluating it requires the whole context as a unit and can short-circuit halfway through the list, depending on if the 'operator' at that position is an uptalk version. The evaluation given this structure can be described in pseudocode: def evaluate(expr): value = expr.atom.evaluate() for trailer in trailers: if trailer.uptalk and value is None: return None value = trailer.evaluate_step(value) The code generation could work the same way, iterating over this and generating whatever instructions each trailer implies. In CPython, The difference between the uptalk and non-uptalk version would be that immediately after the left-hand value is on the stack, insert opcodes: DUP_TOP LOAD_CONST(None) COMPARE_OP(is) POP_JUMP_IF_TRUE(end), with the jump being to the location where the final value of the expression is expected on the stack. Assuming I'm understanding the meaning of each opcode correctly, this sequence would basically be equivalent to a hypothetical JUMP_IF_NONE opcode. I don't think a recursive definition for the structure would work, because evaluating / code-generating an uptalk operator needs to have the top-level expression in order to escape from it to yield None.
![](https://secure.gravatar.com/avatar/047f2332cde3730f1ed661eebb0c5686.jpg?s=120&d=mm&r=g)
Sounds like we're in violent agreement. :-) On Monday, September 28, 2015, Random832 <random832@fastmail.com> wrote:
Guido van Rossum <guido@python.org <javascript:;>> writes:
I would at least define different classes for the uptalk versions.
But my main complaint is using the parse tree as a spec at all
Like I said, I actually came up with that structure *before* seeing that it mirrored a grammar element - it honestly seems like the most natural way to embody the fact that evaluating it requires the whole context as a unit and can short-circuit halfway through the list, depending on if the 'operator' at that position is an uptalk version.
The evaluation given this structure can be described in pseudocode:
def evaluate(expr): value = expr.atom.evaluate() for trailer in trailers: if trailer.uptalk and value is None: return None value = trailer.evaluate_step(value)
The code generation could work the same way, iterating over this and generating whatever instructions each trailer implies. In CPython, The difference between the uptalk and non-uptalk version would be that immediately after the left-hand value is on the stack, insert opcodes: DUP_TOP LOAD_CONST(None) COMPARE_OP(is) POP_JUMP_IF_TRUE(end), with the jump being to the location where the final value of the expression is expected on the stack.
Assuming I'm understanding the meaning of each opcode correctly, this sequence would basically be equivalent to a hypothetical JUMP_IF_NONE opcode.
I don't think a recursive definition for the structure would work, because evaluating / code-generating an uptalk operator needs to have the top-level expression in order to escape from it to yield None.
_______________________________________________ Python-ideas mailing list Python-ideas@python.org <javascript:;> https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
-- --Guido van Rossum (on iPad)
![](https://secure.gravatar.com/avatar/72ee673975357d43d79069ac1cd6abda.jpg?s=120&d=mm&r=g)
Guido van Rossum wrote:
On Mon, Sep 28, 2015 at 12:47 PM, Andrew Barnert <abarnert@yahoo.com <mailto:abarnert@yahoo.com>> wrote:
Expr( value=Attribute( value=Attribute( value=Name(id='spam'), attr='eggs', uptalk=True), attr='cheese', uptalk=False))
Hm, I think the problem is that this way of representing the tree encourages thinking that each attribute (with or without ?) can be treated on its own.
It's hard to think of any other way of representing this in an AST that makes the short-circuiting behaviour any clearer. I suspect that displaying an AST isn't really going to be helpful as a way of documenting the semantics. Because the semantics aren't really in the AST itself, they're in the compiler code that interprets the AST. -- Greg
![](https://secure.gravatar.com/avatar/7e41acaa8f6a0e0f5a7c645e93add55a.jpg?s=120&d=mm&r=g)
On Sep 28, 2015, at 22:55, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Guido van Rossum wrote:
On Mon, Sep 28, 2015 at 12:47 PM, Andrew Barnert <abarnert@yahoo.com <mailto:abarnert@yahoo.com>> wrote: Expr( value=Attribute( value=Attribute( value=Name(id='spam'), attr='eggs', uptalk=True), attr='cheese', uptalk=False)) Hm, I think the problem is that this way of representing the tree encourages thinking that each attribute (with or without ?) can be treated on its own.
It's hard to think of any other way of representing this in an AST that makes the short-circuiting behaviour any clearer.
I suspect that displaying an AST isn't really going to be helpful as a way of documenting the semantics. Because the semantics aren't really in the AST itself, they're in the compiler code that interprets the AST.
That's why I gave both an AST and the bytecode (and how it differs from the AST and bytecode with non-uptalked attribution). I think that makes it obvious and unambiguous what the semantics are, to anyone who knows how the compiler handles attribution ASTs, and understands the resulting bytecode. Of course, as Guido points out, that "anyone who..." is a pretty restricted set, so maybe this wasn't as useful as I intended, and we have to wait for someone to write up the details in a way that's still unambiguous, but also human-friendly.
![](https://secure.gravatar.com/avatar/27c093d0834208f4712faaaec38c2c5c.jpg?s=120&d=mm&r=g)
Glyph tweeted yesterday that everyone should watch the "Nothing is Something" 35' talk by Sandi Metz at RailsConf 2015. It's great and, in a way, relevant to this discussion. https://www.youtube.com/watch?v=29MAL8pJImQ BTW, so far, `or?` is the least horrible token suggested, IMHO. I like the basic semantics, though. Cheers, Luciano On Mon, Sep 28, 2015 at 4:47 PM, Andrew Barnert via Python-ideas <python-ideas@python.org> wrote:
On Monday, September 28, 2015 12:05 PM, Guido van Rossum <guido@python.org> wrote:
On Mon, Sep 28, 2015 at 10:38 AM, Carl Meyer <carl@oddbird.net> wrote:
"Propagating" refers to the proposed behavior where use of ?. or ?[ "propagates" through the following chain of operations. For example:
x = foo?.bar.spam.eggs
Where both `.spam` and `.eggs` would behave like `?.spam` and `?.eggs` (propagating None rather than raising AttributeError), simply because a `.?` had occurred earlier in the chain. So the above behaves differently from:
temp = foo?.bar x = temp.spam.eggs
Which raises questions about whether the propagation escapes parentheses, too:
x = (foo?.bar).spam.eggs
Oh, I see. That's evil.
The correct behavior here is that "foo?.bar.spam.eggs" should mean the same as
(None if foo is None else foo.bar.spam.eggs)
(Stop until you understand that is *not* the same as either of the alternatives you describe.)
I can see the confusion that led to the idea of "propagation" -- it probably comes from an attempt to define "foo?.bar" without reference to the context (in this case the relevant context is that it's followed by ".spam.eggs").
It would really help to have a complete spec, or at least a quick workthrough of how an expression gets parsed and compiled.
I assume it's something like this:
spam?.eggs.cheese becomes this pseudo-AST (I've skipped the loads and maybe some other stuff):
Expr( value=Attribute( value=Attribute( value=Name(id='spam'), attr='eggs', uptalk=True), attr='cheese', uptalk=False))
… which is then compiled as this pseudo-bytecode:
LOAD_NAME 'spam' DUP_TOP POP_JUMP_IF_NONE :label LOAD_ATTR 'eggs' LOAD_ATTR 'cheese' :label
I've invented a new opcode POP_JUMP_IF_NONE, but it should be clear what it does. I think it's clear how replacing spam with any other expression works, and how subscripting works. So the only question is whether understanding how .eggs.cheese becomes a pair of LOAD_ATTRs is sufficient to understand how ?.eggs.cheese becomes a JUMP_IF_NONE followed by the same pair of LOAD_ATTRs through the same two steps.
I suppose the reference documentation wording is also important here, to explain that an uptalked attributeref or subscription short-circuits the whole primary. _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
-- Luciano Ramalho | Author of Fluent Python (O'Reilly, 2015) | http://shop.oreilly.com/product/0636920032519.do | Professor em: http://python.pro.br | Twitter: @ramalhoorg
![](https://secure.gravatar.com/avatar/130fe9f08ce5d2b1716d32438a58c867.jpg?s=120&d=mm&r=g)
On 28.09.2015 23:48, Luciano Ramalho wrote:
Glyph tweeted yesterday that everyone should watch the "Nothing is Something" 35' talk by Sandi Metz at RailsConf 2015. It's great and, in a way, relevant to this discussion.
Nice watch. It's completely in line with our internal guidelines. Great to see that people with practical experience come to the same conclusion. Best, Sven
![](https://secure.gravatar.com/avatar/d6b9415353e04ffa6de5a8f3aaea0553.jpg?s=120&d=mm&r=g)
On 9/28/2015 5:48 PM, Luciano Ramalho wrote:
Glyph tweeted yesterday that everyone should watch the "Nothing is Something" 35' talk by Sandi Metz at RailsConf 2015. It's great and, in a way, relevant to this discussion.
I understood Metz as advocation avoidig the nil (None) problem by giving every class an 'active nothing' that has the methods of the class. We do that for most builtin classes -- 0, (), {}, etc. She also used the identity function with a particular signature in various roles. -- Terry Jan Reedy
![](https://secure.gravatar.com/avatar/130fe9f08ce5d2b1716d32438a58c867.jpg?s=120&d=mm&r=g)
On 29.09.2015 02:38, Terry Reedy wrote:
On 9/28/2015 5:48 PM, Luciano Ramalho wrote:
Glyph tweeted yesterday that everyone should watch the "Nothing is Something" 35' talk by Sandi Metz at RailsConf 2015. It's great and, in a way, relevant to this discussion.
I understood Metz as advocation avoidig the nil (None) problem by giving every class an 'active nothing' that has the methods of the class. We do that for most builtin classes -- 0, (), {}, etc. She also used the identity function with a particular signature in various roles.
I might stress here that nobody said there's a single "active nothing". There are far more "special case objects" (as Robert C. Martin calls it) than 0, (), {}, etc. I fear, however, the stdlib cannot account for every special case object possible. Without None available in the first place, users would be forced to create their domain-specific special case objects. None being available though, people need to be taught to avoid it, which btw. she did a really good job of. Best, Sven
![](https://secure.gravatar.com/avatar/d6b9415353e04ffa6de5a8f3aaea0553.jpg?s=120&d=mm&r=g)
On 9/29/2015 12:35 PM, Sven R. Kunze wrote:
On 29.09.2015 02:38, Terry Reedy wrote:
On 9/28/2015 5:48 PM, Luciano Ramalho wrote:
Glyph tweeted yesterday that everyone should watch the "Nothing is Something" 35' talk by Sandi Metz at RailsConf 2015. It's great and, in a way, relevant to this discussion.
I understood Metz as advocation avoidig the nil (None) problem by giving every class an 'active nothing' that has the methods of the class. We do that for most builtin classes -- 0, (), {}, etc. She also used the identity function with a particular signature in various roles.
I might stress here that nobody said there's a single "active nothing".
Ruby's nil and Python's None are passibe nothings. Any operation other than those inherited from Object raise an exception.
There are far more "special case objects" (as Robert C. Martin calls it) than 0, (), {}, etc.
Metz's point is that there is potentially one for most classes than one might write. Some people have wondered why Python does not come with a builtin identity function. The answer has been that one is not needed much and and it is easy to create one. Metz's answer is that they are very useful for generalizing classes. But she also at least implied that they should be specific to each situation. Certainly in Python, if code were to check signature, and even type annotation, then a matching id function would be needed.
I fear, however, the stdlib cannot account for every special case object possible.
Right. It is not possible to create a null instance of a class that does not yet exist.
Without None available in the first place,
The problem of a general object is that it is general. It should either be a ghost that does nothing, as with None, or a borg than does everything, as with the Bottom of some languages.
users would be forced to create their domain-specific special case objects.
Metz recomends doing this voluntarily ;-) perhaps after an initial prototype.
None being available though, people need to be taught to avoid it, which btw. she did a really good job of.
I think None works really well as the always-returned value for functions that are really procedures. The problem comes with returning something or None, versus something or raise, or something or null of the class of something. -- Terry Jan Reedy
![](https://secure.gravatar.com/avatar/7e41acaa8f6a0e0f5a7c645e93add55a.jpg?s=120&d=mm&r=g)
On Sep 29, 2015, at 14:48, Terry Reedy <tjreedy@udel.edu> wrote:
On 9/29/2015 12:35 PM, Sven R. Kunze wrote:
On 29.09.2015 02:38, Terry Reedy wrote:
On 9/28/2015 5:48 PM, Luciano Ramalho wrote: Glyph tweeted yesterday that everyone should watch the "Nothing is Something" 35' talk by Sandi Metz at RailsConf 2015. It's great and, in a way, relevant to this discussion.
I understood Metz as advocation avoidig the nil (None) problem by giving every class an 'active nothing' that has the methods of the class. We do that for most builtin classes -- 0, (), {}, etc. She also used the identity function with a particular signature in various roles.
I might stress here that nobody said there's a single "active nothing".
Ruby's nil and Python's None are passibe nothings. Any operation other than those inherited from Object raise an exception.
There are far more "special case objects" (as Robert C. Martin calls it) than 0, (), {}, etc.
Metz's point is that there is potentially one for most classes than one might write.
I don't think this is true. First, "int" and "float" are such general-use/low-semantics types that "0" or "0.0" doesn't always mean "nothing". If you're talking about counts, or distances from some preferred origin, then yes, 0 is nothing; if you're talking about Unix timestamps, or ratings from 0 to 5 stars, then it's not. That's exactly why there's so much code in C and such languages that passes around -1 for nothing (but of course that only works when your real data is unsigned but small enough to waste a bit using a signed int), and the fact that Python idiomatically uses None instead of -1 is a strength, not a weakness. Likewise, sometimes "" makes a perfectly good null string, but sometimes it doesn't—it's often worth distinguishing between "" (has no middle name) and None (we haven't asked for the middle name yet), for example. Also, list, set, dict, and most user-defined types are mutable. This means that using [] or Spam() as a type-specific nothing means your nothings are distinct, mutable objects. Sometimes that's OK, sometimes it's even explicitly a good thing, but sometimes it definitely isn't. In a language that encouraged use of more finely-grained types (so you never use "int", you use "Rating", which is constrained to 0-5), the idea that each type that's nullable should have its own null makes some sense, and even more so for a pure-immutable language and idioms around type-driven programming. But that's not even close to Python.
Some people have wondered why Python does not come with a builtin identity function. The answer has been that one is not needed much and and it is easy to create one. Metz's answer is that they are very useful for generalizing classes. But she also at least implied that they should be specific to each situation. Certainly in Python, if code were to check signature, and even type annotation, then a matching id function would be needed.
Having to create an identity function for each type seems like a horrible idea. Even more so in a language that encourages granular typing. Fortunately, any such language that anyone would actually use probably has parametric genericity, so you could just write a single id function from any type A to the same A, and let the compiler deal with specializing it for each type instead of the programmer. (Or you could make type class definitions provide an id by default, or something else equivalent.)
I fear, however, the stdlib cannot account for every special case object possible.
Right. It is not possible to create a null instance of a class that does not yet exist.
Without None available in the first place,
The problem of a general object is that it is general. It should either be a ghost that does nothing, as with None, or a borg than does everything, as with the Bottom of some languages.
It might be worth having both. But maybe not—personally, while I've occasionally created a Python-like ghost in Smalltalk, I've never wanted a Smalltalk-like borg in Python. What I have wanted, quite often, is to write code that locally, explicitly, treats the ghost as a borg. And that's exactly what "is not None" tests are for, and we've all used them. This proposal isn't adding the concept to the language or idiom, just providing syntactic sugar to make an already widely-used feature easier to use.
users would be forced to create their domain-specific special case objects.
Metz recomends doing this voluntarily ;-) perhaps after an initial prototype.
None being available though, people need to be taught to avoid it, which btw. she did a really good job of.
I think None works really well as the always-returned value for functions that are really procedures. The problem comes with returning something or None, versus something or raise, or something or null of the class of something.
I think the problem comes with assuming that there is a universal answer here. Sometimes something vs. None is appropriate. Sometimes, raising is appropriate. Sometimes, returning a special value is appropriate. Occasionally, even building a Maybe type and using that (with or without collapsing) is appropriate (even without syntactic support for pattern matching and fmapping, although it's much nicer with...). Python idiomatically uses all of the first three extensively, differently in different situations, but rarely the fourth. Some languages use a different subset. Suggesting that one of these must always be the answer doesn't seem to be motivated by any real concerns. Does Python code really have a lot more problems with this than C or Swift or some other language that only idiomatically uses one or two different answers for? Even if it does (which I doubt), would eliminating one of the three but changing as little else as possible about the language and ecosystem actually help? And would it be even remotely feasible to do so?
![](https://secure.gravatar.com/avatar/3dd475b8aaa5292d74cb0c3f76c3f196.jpg?s=120&d=mm&r=g)
On Mon, Sep 28, 2015, at 15:47, Andrew Barnert via Python-ideas wrote:
spam?.eggs.cheese becomes this pseudo-AST (I've skipped the loads and maybe some other stuff):
Expr( value=Attribute( value=Attribute( value=Name(id='spam'), attr='eggs', uptalk=True), attr='cheese', uptalk=False))
… which is then compiled as this pseudo-bytecode:
LOAD_NAME 'spam' DUP_TOP POP_JUMP_IF_NONE :label LOAD_ATTR 'eggs' LOAD_ATTR 'cheese' :label
To put this in more concrete terms... What pseudo-AST does (spam?.eggs).cheese end up as, if the notion that uptalk must not escape parentheses is accepted? The pseudo-bytecode is obvious: LOAD_NAME 'spam' JUMP_IF_NONE :label LOAD_ATTR 'eggs' :label LOAD_ATTR 'cheese' [If we're going to define a new opcode, might as well be one that doesn't require a dup] The AST, though, not so much. spam.eggs.cheese and (spam.eggs).cheese are identical: Expr(Attribute(Attribute(Name('spam'), 'eggs'), 'cheese')) And you gave this for the non-parenthesized spam?.eggs.cheese Expr(Attribute(Attribute(Name('spam'), 'eggs', True), 'cheese', False))
I suppose the reference documentation wording is also important here, to explain that an uptalked attributeref or subscription short-circuits the whole primary.
Primary's not the right term here, because a parenthesized expression is also a primary. And so is the first three (first two, first four) terms of a five-dot expression. In the grammar the thing we want is called atom_expr. Why *is* the BNF in the syntax documentation different from the BNF in the grammar anyway?
participants (34)
-
Alexander Belopolsky
-
Andrew Barnert
-
Barry Warsaw
-
Ben Finney
-
Bruce Leban
-
Carl Meyer
-
Chris Angelico
-
Chris Barker
-
Chris Meyer
-
Donald Stufft
-
Emile van Sebille
-
Eric Snow
-
Eric V. Smith
-
Georg Brandl
-
Greg Ewing
-
Guido van Rossum
-
Jeff Hardy
-
John Wong
-
João Bernardo
-
Luciano Ramalho
-
M.-A. Lemburg
-
MRAB
-
Nathan Schneider
-
Nick Coghlan
-
Nikolaus Rath
-
Paul Moore
-
Piotr Duda
-
Random832
-
Rob Cliffe
-
Ron Adam
-
Ryan Gonzalez
-
Steven D'Aprano
-
Sven R. Kunze
-
Terry Reedy