@@ -429,20 +429,43 @@ just because a ``?.`` or ``?[ ]`` is used prior.
429429::
430430
431431 >>> a = None
432- >>> a?.b.c[0].some_function()
432+ >>> print( a?.b.c[0].some_function() )
433433 None
434434
435+ The ``None ``-aware access operators will only short-circuit expressions
436+ containing name, attribute access, subscript, their ``None ``-aware
437+ counterparts and call expressions. As a rule of thumb, short-circuiting
438+ is broken once a (soft-) keyword is reached.
439+
440+ ::
441+
442+ >>> a = None
443+ >>> print(a?.b.c)
444+ None
445+ >>> print(a?.b.c or "Hello")
446+ 'Hello'
447+ >>> 2 in a?.b.c
448+ Traceback (most recent call last):
449+ File "<python-input>", line 1, in <module>
450+ 2 in a?.b.c
451+ TypeError: argument of type 'NoneType' is not a container or iterable
452+ >>> 2 in (a?.b.c or ())
453+ False
454+
435455Grouping
436456********
437457
438- Using ``?. `` and ``?[ ] `` inside groups is possible. Any non-trivial group
439- will be evaluate on its own, short-circuiting will only skip to the end of
440- the the expression inside the group itself.
458+ Grouping is an implicit property of the `Short-circuiting `_ behavior.
459+ If a group contains a non short-circuiting expression, i.e. one that
460+ is not either a name, attribute access, subscript, their ``None ``-aware
461+ counterparts or a call expression, the short-circuiting chain will be
462+ broken. The rule of thumb still applies: short-circuiting is broken once
463+ a (soft-) keyword is reached.
441464
442- ::
443-
444- # Trivial groups
445- (a?.b).c?.d == a?.b.c?.d
465+ In the example below the group contains a `` BoolOp `` (`` or ``) expression
466+ which breaks the short-circuiting chain into two: `` a.b?.c `` inside
467+ the group which is evaluated first and `` (...).e?.func() `` on the
468+ outside.
446469
447470::
448471
@@ -457,6 +480,15 @@ the the expression inside the group itself.
457480 # (...).e?.func()
458481 _t4.func() if ((_t4 := _t3.e) is not None) else None
459482
483+ In contrast, the example below only consists of a name, one attribute
484+ access and two ``None ``-aware attribute access expressions. As such the
485+ grouping does not break the short-circuiting chain. The brackets can
486+ safely be removed.
487+
488+ ::
489+
490+ # Trivial groups
491+ (a?.b).c?.d == a?.b.c?.d
460492
461493Assignments
462494***********
@@ -693,6 +725,33 @@ a try-except block can be used instead.
693725As the ``?. `` and ``?[ ] `` would allow developers to be more explicit in
694726their intend, this suggestion is rejected.
695727
728+ Remove short-circuiting
729+ -----------------------
730+
731+ It was suggested to remove the `Short-circuiting `_ behavior completely
732+ because it might be too difficult to understand. Developers should
733+ instead change any subsequent attribute access or subscript to their
734+ ``None ``-aware variants.
735+
736+ ::
737+
738+ # before
739+ a.b.optional?.c.d.e
740+
741+ # after
742+ a.b.optional?.c?.d?.e
743+
744+ The idea has some of the same challenges as `Add a maybe keyword `_.
745+ By forcing the use of ``?. `` or ``?[ ] `` for attributes which are
746+ ``not-optional ``, it will be difficult to know if the ``not-optional ``
747+ attributes ``.c `` or ``.d `` suddenly started to return ``None `` as well.
748+ The ``AttributeError `` would have been silenced.
749+
750+ Another issue especially for longer expressions is that **all **
751+ subsequent attribute access and subscript operators need to be changed
752+ as soon as just one attribute in a long chain is ``optional ``. Missing
753+ just one can instantly cause a new ``AttributeError `` or ``TypeError ``.
754+
696755``? `` Unary Postfix operator
697756----------------------------
698757
@@ -865,6 +924,41 @@ for lists and tuples and as such would be a valuable addition to the
865924language itself, it doesn't remove the need for arbitrary objects which
866925implement a custom ``__getitem__ `` method.
867926
927+ Limit scope of short-circuiting with grouping
928+ ---------------------------------------------
929+
930+ Some languages like JS [#js_short_circuiting ]_ and C# [#csharp ]_ limit the
931+ scope of the `Short-circuiting `_ via explicit grouping::
932+
933+ a = None
934+ x = (a?.b).c
935+ # ^^^^^^
936+
937+ In the example above short-circuiting would be limited to just ``a?.b ``,
938+ thus with ``a = None `` the expression would raise an ``AttributeError ``
939+ instead of setting ``x `` to ``None ``.
940+
941+ Even though other languages have implemented it that way, this kind of
942+ explicit grouping for short-circuiting does have its disadvantages.
943+ The ``None ``-aware access operators are explicitly designed to return
944+ ``None `` at some point. Directly limiting the scope of the
945+ short-circuiting behavior almost guarantees that the code will raise
946+ an ``AttributeError `` or ``TypeError `` at some point. Type checkers
947+ would also have to raise an error for trying to access an attribute
948+ or subscript on an ``optional `` variable again.
949+
950+ As such breaking the short-circuiting chain does only make sense if a
951+ fallback value is provided at the same time. For example::
952+
953+ (a?.b.c or fallback).e.func()
954+
955+ In case it is known that ``a `` will always be a not ``None `` value,
956+ and it is just still typed as optional, better options include adding
957+ an ``assert a is not None `` or if it is ever proposed a ``Not-None ``
958+ assertion operator ``a! `` (out of scope for this PEP). Developers also
959+ always have the option of splitting the expression up again like they do
960+ today.
961+
868962
869963Common objections
870964=================
@@ -1092,8 +1186,10 @@ Footnotes
10921186 (https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#optional-chaining)
10931187 .. [#js ] JavaScript: Optional chaining (?.)
10941188 (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining)
1189+ .. [#js_short_circuiting ] JavaScript: Optional chaining (?.) - Short-circuiting
1190+ (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining#short-circuiting)
10951191 .. [#csharp ] C# Reference: Member access operators
1096- (https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/member-access-operators)
1192+ (https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/member-access-operators#null-conditional-operators--and- )
10971193 .. [#dart ] Dart: Other operators
10981194 (https://dart.dev/language/operators#other-operators)
10991195 .. [#swift ] Swift: Optional Chaining
0 commit comments