[Python-ideas] Null coalescing operators

Steven D'Aprano steve at pearwood.info
Sat Sep 19 14:06:24 CEST 2015

On Sat, Sep 19, 2015 at 03:17:07AM -0500, C Anthony Risinger wrote:

> I really liked this whole thread, and I largely still do -- I?think -- but
> I'm not sure I like how `?` suddenly prevents whole blocks of code from
> being evaluated. Anything within the (...) or [...] is now skipped (IIUC)
> just because a `?` was added, which seems like it could have side effects
> on the surrounding state, especially since I expect people will use it for
> squashing/silencing or as a convenient trick after the fact, possibly in
> code they did not originally write.

I don't think this is any different from other short-circuiting 
operators, particularly `and` and the ternary `if` operator:

result = obj and obj.method(expression)

result = obj.method(expression) if obj else default

In both cases, `expression` is not evaluated if obj is falsey. That's 
the whole point.

> If the original example included a `?` like so:
>     response = json.dumps?({
>         'created': created?.isoformat(),
>         'updated': updated?.isoformat(),
>         ...
>     })
> should "dumps" be None, the additional `?` (although though you can barely
> see it) prevents *everything else* from executing.

We're still discussing the syntax and semantics of this, so I could be 
wrong, but my understanding of this is that the *first* question mark 
prevents the expressions in the parens from being executed:

json.dumps?( ... )

evaluates as None if json.dumps is None, otherwise it evaluates the 
arguments and calls the dumps object. In other words, rather like this:

_temp = json.dumps  # temporary value
if _temp is None:
    response = None
    response = _temp({
        'created': None if created is None else created.isoformat(),
        'updated': None if updated is None else updated.isoformat(),
del _temp

except the _temp name isn't actually used. The whole point is to avoid 
evaluating an expression (attribute looking, index/key lookup, function 
call) which will fail if the object is None, and if you're not going to 
call the function, why evaluate the arguments to the function?

> This may cause confusion
> about what is being executed, and when, especially once nesting (to any
> degree really) and/or chaining comes into play!

Well, yes, people can abuse most any syntax. 

> Usually when I want to use this pattern, I find I just need to write things
> out more. The concept itself vaguely reminds me of PHP's use of `@` for
> squashing errors.

I had to look up PHP's @ and I must say I'm rather horrified. According 
to the docs, all it does is suppress the error reporting, it does 
nothing to prevent or recover from errors. There's not really an 
equivalent in Python, but I suppose this is the closest:

# similar to PHP's $result = @(expression);
    result = expression
    result = None

This is nothing like this proposal. It doesn't suppress arbitrary 
errors. It's more like a conditional:

# result = obj?(expression)
if obj is None:
    result = None
    result = obj(expression)

If `expression` raises an exception, it will still be raised, but only 
if it is actually evaluated, just like anything else protected by an 
if...else or short-circuit operator.


More information about the Python-ideas mailing list