
I looked at the PEP for None-aware operators and I really feel like they miss one important detail of Python's data model:
{}[0] Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 0 [][0] Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: list index out of range
that is: missing items raise, rather than being None. as such I feel like None-aware operators would encourage ppl to put None everywhere, which from what I can tell, goes completely against Python's data model. however, exception-aware operators could be a potential alternative that: 1. works with existing code (no need to go shoving None everywhere) 2. works in lambdas 3. ends up being more useful? I don't know what the potential syntax would look like. perhaps something along the lines of: exception-raising-expression ?: value-if-exception-raised or: exception-raising-expression ? (IndexError, KeyError, Etc) : value-if-exception-raised but, thoughts?

On Tue, Feb 18, 2020 at 12:20 AM Soni L. <fakedme+py@gmail.com> wrote:
I looked at the PEP for None-aware operators and I really feel like they miss one important detail of Python's data model:
{}[0] Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 0 [][0] Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: list index out of range
that is: missing items raise, rather than being None. as such I feel like None-aware operators would encourage ppl to put None everywhere, which from what I can tell, goes completely against Python's data model.
however, exception-aware operators could be a potential alternative that:
1. works with existing code (no need to go shoving None everywhere) 2. works in lambdas 3. ends up being more useful?
I don't know what the potential syntax would look like. perhaps something along the lines of:
exception-raising-expression ?: value-if-exception-raised
or:
exception-raising-expression ? (IndexError, KeyError, Etc) : value-if-exception-raised
but, thoughts?
See PEP 463 and the many MANY posts about it. ChrisA

On Sun, Feb 16, 2020 at 10:07:13PM -0300, Soni L. wrote:
I looked at the PEP for None-aware operators and I really feel like they miss one important detail of Python's data model: [...] that is: missing items raise, rather than being None.
You are conflating two distinct cases. Your example `{}[0]` is the case where the mapping is there, but the key is missing. You can already solve that with `{}.get(0, default)`. The None-aware use-case is *the mapping itself* is missing. In Python, we don't have an `undefined` special value, like in Javascript, we use None for the same purpose. Of course nobody is going to write the literal `None[key]`, but there are plenty of cases where we need to distinguish between "there is no dict" and "there is a dict, but it happens to be empty".
as such I feel like None-aware operators would encourage ppl to put None everywhere, which from what I can tell, goes completely against Python's data model.
Can you explain why you think that using None goes against Python's data model? None is part of Python's data model; it is an object, like everything else, and it has been used to represent "missing data" and similar cases since Python 1.5 and older. -- Steven

On 2020-02-17 1:04 p.m., Steven D'Aprano wrote:
On Sun, Feb 16, 2020 at 10:07:13PM -0300, Soni L. wrote:
I looked at the PEP for None-aware operators and I really feel like they miss one important detail of Python's data model: [...] that is: missing items raise, rather than being None.
You are conflating two distinct cases. Your example `{}[0]` is the case where the mapping is there, but the key is missing. You can already solve that with `{}.get(0, default)`.
The None-aware use-case is *the mapping itself* is missing. In Python, we don't have an `undefined` special value, like in Javascript, we use None for the same purpose.
If the mapping itself is missing, then:
foo[0] Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'foo' is not defined
(or AttributeError sometimes)
Of course nobody is going to write the literal `None[key]`, but there are plenty of cases where we need to distinguish between "there is no dict" and "there is a dict, but it happens to be empty".
as such I feel like None-aware operators would encourage ppl to put None everywhere, which from what I can tell, goes completely against Python's data model.
Can you explain why you think that using None goes against Python's data model? None is part of Python's data model; it is an object, like everything else, and it has been used to represent "missing data" and similar cases since Python 1.5 and older.
I feel like None-as-undefined goes against Python's data model because we have NameError, AttributeError, KeyError, IndexError, and probably others, and I feel like those do a better job of representing undefined.

On Feb 17, 2020, at 05:21, Soni L. <fakedme+py@gmail.com> wrote:
missing items raise, rather than being None. as such I feel like None-aware operators would encourage ppl to put None everywhere, which from what I can tell, goes completely against Python's data model.
I don’t think this is entirely true. None is part of Python’s data model, and the sort of duality between None returns and exceptions is already baked in everywhere (even in dict.__getitem__ vs. dict.get). None-aware operators wouldn’t be creating a new problem, it would be feeding into the existing data model. The problem is that the data model is theoretically implausible, but practically works pretty nicely because care has been put into what’s exceptional and what’s just missing, and that’s a fragile balance that a new feature can throw off. And maybe throwing that balance off is part of why people couldn’t make None-aware operators feel right as part of Python—but exception-aware operators have the same risk. (And there’s no obvious way to do both that feels consistent.)
however, exception-aware operators could be a potential alternative that:
1. works with existing code (no need to go shoving None everywhere) 2. works in lambdas 3. ends up being more useful?
I don't know what the potential syntax would look like.
There is something to this idea, but coming up with a good syntax is hard. You often want to handle just one exception, not all—e.g., in your example, you want a LookupError to be captured and turned into a value, but not a TypeError. But other times you might want to actually swallow everything. Some relevant solutions and problems came up in the discussions around try-except expressions. You can’t always chain exception-defaulting the same way you can chain None. The alternate result for every None-chaining operator is None, and at each step on the chain you’re testing for None again; the alternate result for exception-chaining operators would be whatever you specify, but the next step is testing for an exception, not your default value, or any other value. There’s also all the issues with None-aware expressions, many of which would also apply here. Also, consider None-aware assignment. If a missing value is missing because it’s None, it’s obvious what to do. If it’s missing because your earlier assignment failed, it’s often not just a single thing that’s missing—e.g., a ValueError from unpacking is probably more common than an IndexError from assigning into a list. And, worse, you can’t tell that it failed later—the name might have an old value lying around, or it might be a NameError or UnboundLocalError, but even if you know which of those to check for, you don’t know that it came from a failed assignment instead of, say, from an assignment that only happens on an if branch that wasn’t taken. With None, you know the value None is in the name because you can just look it up and get None back. And there may be other problems. Maybe they’re solvable, but I think you need to work through a complete proposal, with all of the edge cases explored, and multiple realistic examples, before you can really convince yourself (or anyone else) they’re solved. While you’re at it: I considered this idea a few months ago when I was looking for a way to do the equivalent of Swift “if let” and similar features from other languages with the walrus operator: do one thing if unpacking an Iterable succeeds, do a different thing if it raises a ValueError. Swift uses this in some places other languages would use nil-chaining by just if-letting an assignment from a Spam-or-nil variable to a Spam variable (which raises on nil), as well as for unpacking cases. I couldn’t get either to fit usefully into Python (at least not with an expression rather than a statement), but it feels like something useful should be doable here, I just couldn’t find it. (Although it still wouldn’t allow me to chain up multiple if let/elif let/etc. as I was hoping to do for pattern matching, a single if-let expression or statement seems like it should be useful for other things anyway.) Anyway, if you’re willing to read over all the threads on None-chaining and try-except expressions and try to put something together that answers all of the anticipated problems and has good uses, I think that could be worth doing. You might get stuck and end up having wasted your time, but you might come up with a great new feature.
participants (4)
-
Andrew Barnert
-
Chris Angelico
-
Soni L.
-
Steven D'Aprano