All,
I would like to revive PEP 505, which proposes null-coalescing via ?.
, ??
, and ??=
. I believe this set of operators fills an ergonomic gap in the language where users are currently forced to choose between the footguns associated with or
and the verbosity of if else
expressions. I am hoping to solicit feedback on how I can get the processing moving again.
Some Thoughts on the Original PEP
- In my opinion, PEP 505 does not sufficiently emphasize why
None
is special. While truthiness is a convenient measure of numbers and containers, the only semantically correct way to distinguish arbitrary values fromNone
is by identity. This entails a verbose and oft-repeated boilerplatefoo if foo is not None else bar
when inferring defaults, operating on optional variables, and accessing optional attributes. Users must not reach foror
in these scenarios because0
,""
, and sometimes even()
,[]
, and{}
have intrinsic meanings that would be clobbered by their apparent falsiness (this is an aforementioned footgun). - In the next iteration of this PEP, I would weakly prefer to omit the
?[]
operator. In my experience, its use case is much rarer than that of the other operators and does not merit the work to specify or teach it. I could be convinced to omit??=
as well, although I find this one to be much less invasive as it follows the well-trod path of the other binary operators.
Initial Address of Counterarguments
Skimming the related email thread, I found that the few cogent counterarguments fell into the following categories:
- It looks less Pythonic, it introduces line noise, it’s ugly: it is my strong opinion that the aesthetic changes introduced by this feature are no more invasive or alien than those of the walrus operator
:=
from PEP 572 or the matrix multiplication operator@
from PEP 465. Of these three features, I would actually expect the null-coalescing operators to be the most easily-identified by the average user thanks to their prevalence in modern programming languages. I feel that complaints about the illegibility ofa?.b?.c?.d
are contrived and exaggerated, as I expect chaining two or more conditionals to be vanishingly rare. Moreover, I would like to suggest that these operators actually reduce line noise whereif else
expressions are necessary. - These operators cannot be overridden with magic methods: the ability to override
__bool__
(beyond the fact that0
,""
, etc. are falsy) is why truthiness checks are not sufficient to distinguish arbitrary values fromNone
. Considerx
in the body of somedef foo(x: Bar = None): ...
: one cannot determine whetherx
was specified by testing for truthiness, e.g.if x:
, because even ifBar
does not override__bool__
itself, its subclasses might, and in doing so, may returnFalse
. Consequently, it is semantically incorrect to provide a defaultBar
viax = x or Bar()
, whereasx ??= Bar()
works as expected. The correctness of the latter statement hinges on null-coalescing operators beingNone
-specific and un-overridable. - Python is evolving too quickly: I am not involved enough with core development to know whether this is still the case, but it has been many years since PEP 505 was published and this discussion was held. I would appreciate feedback here.
- The grammatical implementation is flawed or has unintuitive behavior: I plan on digging into this once it’s clear that a re-proposal won’t be immediately shot down, as it will require more careful, academic work.
Going Forward
I am fairly new to the PEP ecosystem and its customs. Above all else, I am looking for guidance on how to best advocate for null coalescing operators. More concretely, I would like to use this thread to:
- Get a read on the community’s current disposition towards
?.
,??
, and??=
- Work towards addressing any current counterarguments
If there is significant positive sentiment, I will begin working through the following incrementally:
- Iterating on the grammatical specification of these operators
- Updating the reference implementation
- Redrafting the PEP
This PEP deals heavily with subjective notions like aesthetics, readability, and intuition. I implore thread participants to preface their opinions with phrases like “I think”, “I believe”, and “I feel that” to keep the discussion grounded in what is real and what is perceived. Demonstrating that a null-coalescing operator precedence introduces a bug in existing code is objective. Suggesting that the precedence is confusing is subjective and should be disclaimed because it should be justified and may be argued.
Amendment 1: Specification of Operators
As I see it, the new operators should serve purely as syntax sugar for the following expansions (ignoring precedence for now):
a?.b === a.b if a is not None else None
a ?? b === a if a is not None else b
a ??= b === if a is None: a = b
Please refer to this specification in the followup discussion, but note that it’s not final and that I’m happy to entertain alternative proposals (which I’ll continually append here).
Amendment 2: Example Usage
>>> a = None
>>> a?.foo # `a` is None so attribute access is elided, returning None
>>> a ?? 0
0
>>> a ??= 0 # `a` is now 0, equivalent to `a = a ?? 0`
>>> a ?? b # `a` is not None, so evaluation of `b` is elided and the value of `a` is returned
0
>>> b ?? a # `b` is not defined, so raise
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'b' is not defined
>>> a?.as_integer_ratio()
(0, 1)
>>> a?.foo # `a is not None` so attribute access occurs normally
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'int' object has no attribute 'foo'