[Python-ideas] PEP 505: None-aware operators

Steve Dower steve.dower at python.org
Mon Jul 23 05:51:31 EDT 2018


Responding to a few more ideas that have come up here. Again, apologies 
for not directing them to the original authors, but I want to focus on 
the ideas that are leading towards a more informed decision, and not 
getting distracted by providing customised examples for people or 
getting into side debates.

I'm also going to try and update the PEP text today (or this week at 
least) to better clarify some of the questions that have come up (and 
fix that embarrassingly broken example :( )

Cheers,
Steve

False: '?.' should be surrounded by spaces
------------------------------------------

It's basically the same as '.'. Spell it 'a?.b', not 'a ?. b' (like 
'a.b' rather than 'a + b').

It's an enhancement to attribute access, not a new type of binary 
operator. The right-hand side cannot be evaluated in isolation.

In my opinion, it can also be read aloud the same as '.' as well (see 
the next point).

False: 'a?.b' is totally different from 'a.b'
---------------------------------------------

The expression 'a.b' either results in 'a.b' or AttributeError (assuming 
no descriptors are involved).

The expression 'a?.b' either results in 'a.b' or None (again, assuming 
no descriptors).

This isn't a crazy new idea, it really just short-circuits a specific 
error that can only be precisely avoided with "if None" checks (catching 
AttributeError is not the same).

The trivial case is already a one-liner
---------------------------------------

That may be the case if you have a single character variable, but this 
proposal is not intended to try and further simplify already simple 
cases. It is for complex cases, particularly where you do not want to 
reevaluate the arguments or potentially leak temporary names into a 
module or class namespace.

(Brief aside: 'a if (a := expr) is not None else None' is going to be 
the best workaround. The suggested 'a := expr if a is not None else 
None' is incorrect because the condition is evaluated first and so has 
to contain the assignment.)

False: ??= is a new form of assignment
--------------------------------------

No, it's just augmented assignment for a binary operator. "a ??= b" is 
identical to "a = a ?? b", just like "+=" and friends.

It has no relationship to assignment expressions. '??=' can only be used 
as a statement, and is not strictly necessary, but if we add a new 
binary operator '??' and it does not have an equivalent augmented 
assignment statement, people will justifiably wonder about the 
inconsistency.

The PEP author is unsure about how it works
-------------------------------------------

I wish this statement had come with some context, because the only thing 
I'm unsure about is what I'm supposed to be unsure about.

That said, I'm willing to make changes to the PEP based on the feedback 
and discussion. I haven't come into this with a "my way is 100% right 
and it will never change" mindset, so if this is a misinterpretation of 
my willingness to listen to feedback then I'm sorry I wasn't more clear. 
I *do* care about your opinions (when presented fairly and constructively).

Which is the most important operator?
-------------------------------------

Personally, I think '?.' is the most valuable. The value of '??' arises 
because (unless changing the semantics from None-aware to False-aware) 
it provides a way of setting the default that is consistent with how we 
got to the no-value value (e.g. `None?.a ?? b` and `""?.a ?? b` are 
different, whereas `None?.a or b` and `""?.a or b` are equivalent).

I'm borderline on ?[] right now. Honestly, I think it works best if it 
also silently handles LookupError (e.g. for traversing a loaded JSON 
dict), but then it's inconsistent with ?. which I think works best if it 
handles None but allows AttributeError. Either way, both have the 
ability to directly handle the exception. For example, (assuming e1, e2 
are expressions and not values):

     v = e1?[e2]

Could be handled as this example (for None-aware):

     _temp1 = (e1)
     v = _temp1[e2] if _temp1 is not None else None

Or for silent exception handling of the lookup only:

     _temp1 = (e1)
     _temp2 = (e2)
     try:
         v = _temp1[_temp2] if _temp1 is not None else None
     except LookupError:
         v = None

Note that this second example is _not_ how most people protect against 
invalid lookups (most people use `.get` when it's available, or they 
accept that LookupErrors raised from e1 or e2 should also be silently 
handled). So there would be value in ?[] being able to more precisely 
handle the exception.

However, with ?. being available, and _most_ lookups being on dicts that 
have .get(), you can also traverse JSON values fairly easily like this:

     d = json.load(f)
     name = d.get('user')?.get('details')?.get('name') ?? '<no name>'

With ?[] doing the safe lookup as well, this could be:

     d = json.load(f)
     name = d?['user']?['details']?['name'] ?? '<no name>'

Now, my *least* favourite part of this is that (as someone pointed out), 
it looks very similar to using '??' with a list as the default value. 
And because of that, I'm okay with removing this part of the proposal if 
it is unpopular.


More information about the Python-ideas mailing list