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.
_______________________________________________
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to