-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
PEP 823: None-aware access operators #4798
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
@gvanrossum Please can you confirm you're sponsoring this PEP? |
Yes. |
gvanrossum
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here are a ton of nits. I am strongly in favor of this proposal -- I just think the main exposition is a bit unwieldy. Thanks for writing this Marc!
Note that I didn't get to the Deferred/Rejected ideas yet. Some of the things I balk over in the main exposition (like using the walrus or match/case) might find a place in the latter.
|
|
||
| An attribute or value is ``optional`` | ||
| In the context of this PEP an attribute or value is considered | ||
| ``optional`` if it is always present but can be ``None``. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice -- this matches exactly what Optional[X] means in the type notation.
| An attribute or value is ``missing`` | ||
| An attribute or value is considered ``missing`` if it is not present | ||
| at all. For ``typing.TypedDict`` these would be ``typing.NotRequired`` | ||
| keys when they are not preset. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| keys when they are not preset. | |
| keys without a default. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could be missing something, but I think TypedDicts don't have any default values. Keys can either be present or absent.
"without a default" would apply to dataclasses.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then what does "without a preset" mean?
| class Person: | ||
| emails: list[str] | None | ||
|
|
||
| def get_person_email(sensor: Sensor) -> str | None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly this example is overly complex (five classes before you get to the point!) and quite unrealistic. I also don't understand what real-world scenario this represents (what is the relationship between Sensor and Machine; and what is a Line?).
I think it would be sufficient to show the semantics of a?.b?.c, which is
None if a is None else b?.cwhich expands to
None if a is None else (None if (_t := a.b) is None else _t.cCombining this with ?[ ] is inherently complicated because this is much less common. It would be sufficient to add a phrase like "and similar for a?[b]?[c] or a?[b]?.c or a?.b?[c]".
Finally, I would never write such code that way -- I'd probably use a bunch of nested or serial if-statements, for readability.
| some point, the function would just return ``None``. This is problematic | ||
| since ``None`` is a valid return value already. Thus this would not raise | ||
| an exception in the caller and even type checkers would not be able to | ||
| detect it. The solution here is to compare with ``None`` instead. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly the first version of the code should just compare to None everywhere, so you won't have to write this paragraph and the next plus the refined example (or you can just start with the latter). We don't need to have a whole treatise about coding style in this PEP. And honestly, except in the case of objects that behave like a container (like lists), an object evaluating to None is an anti-pattern where I'm concerned.
| ^^^^ | ||
| SyntaxError: cannot assign to none aware expression | ||
|
|
||
| It is however possible to use them in `groups <Grouping>`_, though care |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about (a?.b) = 1 ??? It should fail, but the text here does not say so explicitly, I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That will fail. Similar to (None) = 1 and (True) = 1. The t_primary grammar rule isn't changed.
Will see if I can point that out more clearly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The latter fail during parsing, with a SyntaxError. Will (a?.b) = 1 also be a SyntaxError? I think it can be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It already is. It's actually the same even the same error message as for (None) = 1 since it's triggered in the same code path during parsing.
>>> a = None
>>> (a?.b) = 1
File "<python-input-1>", line 1
(a?.b) = 1
^^^^
SyntaxError: cannot assign to none aware expression here. Maybe you meant '==' instead of '='?I just updated both PEPs to fix the links to the reference implementations and the (shared) demo. Feel free to try it out yourself: https://pep823-and-pep824-demo.pages.dev/
|
|
||
| After students know how the attribute access ``.`` and subscript ``[ ]`` | ||
| operators work, they may learn about the "``None``-aware" versions for | ||
| both. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly I'd only teach them ?. and wait until they ask if there's an equivalent for [ ] before telling them that.
cdce8p
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the detailed feedback! I'd like to address your general comments regarding the motivation section here directly instead of splitting it up for each comment.
While I was preparing to write this PEP, I reread a lot of the discussions from the last few years. One of the most common arguments against these operators was that they are basically just syntactic sugar and don't add anything new. Technically speaking this is correct. We both know that these would still be great additions and make certain coding patterns a lot easier to write. However, the question I asked myself was how can I best explain why all the other patterns are only suboptimal.
I ended up with the structure you read. It starts at "beginner" level with simple truthiness checks. I've actually seen code like this in production. Sometimes lazy code is enough even if it contains problems. It continues with the explicit is not None checks, before ultimately adding the assignment expressions as well. try and match were added as these were the other often suggested builtin alternatives.
By showcasing how each of these work, I can point out the subtle issues each option has and provide a justification why ?. and ?[ ] should be added.
In the end I also wanted to avoid falling into the same, IMO trap, PEP 505 did with just showing nice examples and basically saying the new operators are just better. https://peps.python.org/pep-0505/#examples That's also why I deliberately moved the common patterns towards the end of the motivation section.
--
I admit the example isn't ideal. It's adopted from a discussion post I saw. However, I wanted to add some "real" class and variable names for these examples. Of course, a?.b.c?[d].e would work as well, but that oftentimes seems contrived and far away. PEP 505 did that and I always found that somewhat unintuitive, even though it's strait forward. https://peps.python.org/pep-0505/#the-maybe-dot-and-maybe-subscript-operators
I'm happy to change it if we can come up with a better example.
| # a.b?.c() | ||
| _t.c() if ((_t := a.b) is not None) else None | ||
|
|
||
| # a?.b?.c.d |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you point out somewhere that effectively this is equivalent to
(a?.b)?.c.d
Only implicitly in the grouping section.
because that's an easy-to-remember way to recall the relative priority of multiple
?operators in the same primary expression.
I'm not sure priority is a good concept here. The expressions are evaluated left to right like it's the case with "normal" access operators. Each is effectively evaluated on its own, one after the other. Tbh I tried to avoid adding grouping to the examples as much as possible, aside from the dedicated section. I feel like thinking about them just complicates things unnecessarily.
I'll think about this some more if it makes sense to point out somewhere.
| ^^^^ | ||
| SyntaxError: cannot assign to none aware expression | ||
|
|
||
| It is however possible to use them in `groups <Grouping>`_, though care |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That will fail. Similar to (None) = 1 and (True) = 1. The t_primary grammar rule isn't changed.
Will see if I can point that out more clearly.
| at runtime otherwise a ``TypeError`` is raised. This behavior is similar | ||
| to awaiting any other variable which can be ``None``. | ||
|
|
||
| AST changes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically both (?) though it's really just the three changes listed below
- Added
NoneAwareAttributenode - Added
NoneAwareSubscriptnode - Added
groupattribute for all expression nodes.
You're right to push back -- am taking a pretty extreme POV here. About syntactic sugar: half of Python is syntactic sugar. We don't need My main feeling about the long exposition in the Motivation section is that many of the things you show are either just bad habits (no matter how often people write them -- this PEP isn't the place to educate them), or straw-men examples that look like they are only shown to shoot them down (the match/case version). If people have brought these up in the discussion and you want to capture that, maybe you can move them to the "Rejected ideas" section with subheadings like "Do nothing -- use match/case (or try/except, or whatever other suggestion was made)". How do you feel about that option? If you don't like that either, I'll approve your current approach -- I don't want to die on this hill. :-) |
I'll think about this some more. You might not have gotten around to it yet but I do also have added a |
| None | ||
|
|
||
| The ``None``-aware access operators will only short-circuit expressions | ||
| containing name, attribute access, subscript, their ``None``-aware |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does name refer to? If you're thinking of .name, that's already covered by "attribute access". If you're thinking of name?.b.c, name is never skipped.
Basic requirements (all PEP Types)
pep-NNNN.rst), PR title (PEP 123: <Title of PEP>) andPEPheaderAuthororSponsor, and formally confirmed their approvalAuthor,Status(Draft),TypeandCreatedheaders filled out correctlyPEP-Delegate,Topic,RequiresandReplacesheaders completed if appropriate.github/CODEOWNERSfor the PEPStandards Track requirements
Python-Versionset to valid (pre-beta) future Python version, if relevantDiscussions-ToandPost-History📚 Documentation preview 📚: https://pep-previews--4798.org.readthedocs.build/