[Python-ideas] PEP 505 (None coalescing operators) thoughts

Nick Coghlan ncoghlan at gmail.com
Thu Oct 1 09:59:11 CEST 2015


On 1 October 2015 at 12:39, Andrew Barnert via Python-ideas
<python-ideas at python.org> wrote:
> On Sep 30, 2015, at 18:08, MRAB <python at 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 at gmail.com   |   Brisbane, Australia


More information about the Python-ideas mailing list