[Python-ideas] Arguments to exceptions

Stephen J. Turnbull turnbull.stephen.fw at u.tsukuba.ac.jp
Mon Jul 10 07:36:36 EDT 2017


Jeff Walker writes:
 > Stephen,

Mr. d'Aprano is a "Steven."  I'm a "Stephen".  We both go by "Steve".
And you replied to Chris, not to Steve.  It's worth being careful
about these things.

I don't see what I would consider satisfactory, concise answers to
your questions about Steve's claims about defects in Ken's proposal.
So I'll take a somewhat belated hack at it myself.  Warning, I don't
do so well on "concise", after all. ;-)

 > >  the idea doesn't actually solve the problem it is intended to
 > 
 > Specifically Ken started by saying that it should not be necessary
 > to parse the messages to get the components of the message. He then
 > gave an example where he was able to access the components of the
 > message without parsing the message. So how is it that he is not
 > solving the problem he intended to solve?

Changing the convention for raising NameError to

>>> raise NameError("foo", "is undefined")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: ('foo', 'is undefined')

provides all of the functionality of Ken's BaseException with the
default template of None.  Thus, it's not Ken's BaseException that
solves the problem, it's the required change to the convention for
raising NameError.  Ken's BaseException does provide additional
functionality (the template feature, allowing a pretty error message),
but that's irrelevant to the actual solution of instantiating
Exceptions with unformatted argument(s).  Note that even

>>> raise NameError('foo')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: foo

isn't actually terrible in the interpreter, and would give Ken a more
reliable way to extract the name (e.args[0]).  Of course there's a
good chance that wouldn't work so well for arbitrary Exceptions, but
the multiple argument, ugly but parseable message, approach usually
should work.

 > >  His solution can't work
 > 
 > Again, he gave an example where he was able to access the
 > components of the message without parsing the message. Yet you
 > claim his solution cannot work.  Is his example wrong?

This claim is missing three words: "in full generality".  This is
demonstrated by Steven's "nym" example, which is a general problem
that can occur in any raise statement.  I think this indicates that
you really want to provide a custom API for each and every derived
Exception.  Can't enforce that, or even provide a nice generic
default, in BaseException as Ken proposed to revise it.

There's also the rather tricky question of backward compatibility.  In
particular, any use of this feature in existing raise statements that
involves changing the content of Exception.args will break code that
parses the args rather than stringifying the Exception and parsing
that.  I think this breakage is likely to be very tempting when people
consider "upgrading" existing raise statements with preformatted
string arguments to "structured" arguments containing objects of
arbitrary types.  And any change to the format string (such as
translating to a different language where the name appears in a
different position) would imply the same breakage.

Finally, while it's unfair to judge the whole proposal on proof-of-
concept code, his BaseException is inadequate.  Specifically, the
template argument should get a decent default ("{:s}" seems
reasonable, as we've seen above), and given that some arguments are
likely to be strings containing spaces, his generic __str__ is going
to confuse the heck out of users at the interpreter.  Maybe it's
really as easy as indicated here to fix those problems, but I wouldn't
bet on it.

To address all of the above problems, it seems reasonable to me to
follow PEP 352's advice and initialize attributes on the exception
from the arguments to the constructor.  That plus custom __str__s to
deal with nice formatting in tracebacks would make changes to
BaseException unnecessary, although the addition of the template
feature *might* be useful to reduce boilerplate in __str__, or even
allow BaseException to provide a widely useful generic __str__.  I'm
doubtful it would be that widely useful, but who knows?

 > >  He hasn't demonstrated that there is a real problem
 > 
 > You yourself admitted that parsing a message to extract the
 > components is undesirable.

Sure, but again two words are missing from the claim: "in
BaseException".  The claim is that "parse a string" is a problem in
the definitions of *derived* exceptions, and in the conventions used
to raise them.  Programmers writing inadequate code is a real problem,
but not one that Python (the language) can fix.  (Of course we can
submit bug reports and PEPs and fix our own inadequate code!)

Another way to express this point is that we know that the raise
statements will have to be fixed *everywhere*, and that some __str__s
will need to be changed or added.  The claim "hasn't demonstrated" is
meant to say "a problem that can be fixed by changing BaseException".
It seems likely to me that there are going to be a pile of slightly
different ways to address this depending on the particular exception
in question, and many of the more important ones (eg, OSError) are
already structured and won't benefit from this change at all.

Finally, generic experience has shown that the closer to root in one
of these hierarchies you go, the more conservative you should be.  The
ramifications of a change in a very general feature for older code, as
well as for more derived components, are often very hard to foresee
accurately, and most surprises are unpleasant.  Specifically for
BaseException, Nick points out that a lot of corner cases were
considered for exception hierarchies, and it was decided at that time
that this kind of thing was rather risky for the foreseeable benefit.
That doesn't mean it's still so risky, but I think going slow here,
and doing our best to beat the proposal into a twisting, steaming
hunk of half-melted metal is warranted.  If it's still holding its
shape after attacks with precision-guided munitions, then the risk is
more likely to be worth it. :-)

Steve



More information about the Python-ideas mailing list