:

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.

 -[]z.
_______________________________________________
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