Disclaimer: I haven't followed all of this discussion, so some or all of the following may already have been expressed better (and perhaps refuted better still).
On Wed, 2016-11-02 at 08:46 -0700, Guido van Rossum wrote:
[...] we need to agree on what even the right definition of ?. is. It's been frighteningly difficult to explain this even on this list, even though I have a very clear view in my head, and PEP 505 also has the same semantics and explains well why those are the right semantics.
I think the proposed semantics for ?. are confusing (and the operator itself dangerous, but more on that later).
If I write something like obj.attr, the failure mode I care about is that obj has no attribute attr, rather than that obj is specifically None (or one of a defined group of somewhat Nonelike objects).
Clearly, in such a circumstance, obj is not what I expected it to be, because I thought it was going to have an attribute attr, and it doesn't.
Whether that's because it's None, some other Nonelike object, False, or some other type of object altogether may or may not be relevant, but is certainly secondary to the fact that there's no attr for me to work with.
Most often I will want the default behaviour that an AttributeError is raised, but often it would be useful to say I'd like some kind of default value back with e.g. getattr(obj, 'attr', default), and sometimes it would be useful for that default value to propagate through a chain of attribute accesses without having to write a convoluted mess of nested getattr calls.
In the latter case it usually does not matter to me what type obj is, except that if there *were* some syntax in Python that allowed me to propagate a default value through a chain of failed attribute accesses, it would be quite frustrating to find that I couldn't use it because obj happened not to be one of an anointed cohort of Nonelike objects.
I'm sure it's obvious by now that I think the intuitive behaviour of any ?. operator is to suppress AttributeError on the right hand side, rather than check for nullity on the left.
Maybe this reveals naïvety on my part regarding some inherent design or implementation difficulty with suppressing errors on the right rather than checking nullity on the left. However, I think "my" semantics are at least as likely to be assumed by a non-expert as the alternative proposed in this thread, and that's going to cause a lot of confusion.
... which leads to a second point: this is dangerous. I'm going to try to be tactful here, bearing in mind Nick Coghlan's well-argued imprecations against denigrating other languages, but:
I recently spent a few months writing Ruby for a living. Ruby has a &. operator with similar semantics to those proposed, as well as (in ActiveSupport) an obj.try(:attr) method closer to the exception-catching model described above.
It has both of those things, as far as I can tell, because of an IMO misguided preference in the Rails world for returning nil rather than raising exceptions, which tends to cause errors to materialize far from their source.
When errors materialize far from their source, they are harder to debug, and it can be very tempting to use the likes of &. and try() as band- aids, rather than thoroughly investigate the causes of such errors. The end result of this tendency is that errors materialize further still from their source, and code gets progressively harder to debug over time.
I worry that introducing such band-aids into Python will encourage similar behaviour, and for that reason I'm opposed to introducing an operator with either null-coalescing or exception-suppressing behaviour.
Further, if a library encourages chained attribute accesses which may fail mid-chain, then I would rather that library take responsibility for the consequences of its design at the cost of some magic (e.g. by catching and handling AttributeError) than introduce confusing new syntax into the language.
However, I do think an alternative is possible: extend getattr().
getattr(obj, ('chain', 'of', 'attributes'), default)
This is more verbose than an operator, but I think its intent is clearer, and its verbosity makes it less likely to be misused. If extending getattr is unacceptable, a new dig() function (either as a builtin or part of the stdlib) with the same semantics would work just as well.
If this has already been proposed here, I apologise (and would appreciate a pointer to arguments against it, if anyone can find them without too much effort).
Incidentally, Chris Angelico's PEP 463 "Exception-catching expressions" could result in a clearer-still idiom:
(obj.chain.of.attributes except AttributeError: default)
... but I suppose that ship has sailed.