[Python-ideas] Arguments to exceptions

Steven D'Aprano steve at pearwood.info
Thu Jul 6 14:56:32 EDT 2017

On Thu, Jul 06, 2017 at 01:59:02PM -0400, Mark E. Haase wrote:
> On Thu, Jul 6, 2017 at 5:58 AM, Paul Moore <p.f.moore at gmail.com> wrote:
> > To use the (already
> > over-used) NameError example, Ken's proposal doesn't include any
> > change to how NameError exceptions are raised to store the name
> > separately on the exception.
> >
> Maybe I'm misunderstanding you, but the proposal has a clear example of
> raising NameError and getting the name attribute from the exception
> instance:
>     try:
>         raise NameError(name=name, template="name '{name}' is not defined.")
>     except NameError as e:
>         name = e.kwargs['name']
>         msg = str(e)
>         ...

What prevents the programmer from writing this?

raise NameError(nym=s, template="name '{nym}' is not defined.")

Or any other keyword name for that matter. Since the exception class 
accepts arbitrary keyword arguments, we have to expect that it could be 
used with arbitrary keyword arguments.

Only the exception subclass knows how many and what information it 

- NameError knows that it expects a name;
- IndexError knows that it expects an index;
- OSError knows that it expects anything up to five arguments
  (errno, errstr, winerr, filename1, filename2);

etc. BaseException cannot be expected to enforce that. Ken's suggestion 
to put the argument handling logic in BaseException doesn't give us any 
way to guarantee that NameError.kwargs['name'] will even exist, or that 
NameError.args[0] is the name.

> > Yes. Because he tries to extract the name component of a NameError,
> > and yet that component isn't stored anywhere - under his proposal or
> > under current CPython.
> >
> I'm not sure what you mean by "extract", but the proposal calls for the
> name to be passed as a keyword argument (see above) and stored in
> self.kwargs:
>     class BaseException:
>         def __init__(self, *args, **kwargs):
>             self.args = args
>             self.kwargs = kwargs

Keyword *or positional argument*.

Even if given as a keyword argument, it could use any keyword. That's a 
problem. Assuming it will be "name" is fragile and barely any better 
than the status quo, and it's harder to use than named attributes.

If there is a need for NameError to make the name programmably 
discoverable without scraping the error message, then as PEP 352 
recommends, it should be made available via an attribute: err.name, not 
err.args[0] or err.kwargs['name'].

Here's a proof of concept of the sort of thing we could do that is 
backwards compatible and follows PEP 352:

class NameError(Exception):
     def __init__(self, *args):
         self.args = args
         if len(args) == 2:
             self.name = args[0]
             self.name = None
     def __str__(self):
         if len(self.args) == 1:
             return str(self.args[0])
         elif len(self.args) == 2:
             return "[{}] {}".format(*self.args)
         elif self.args:
              return str(self.args)
         return ''


More information about the Python-ideas mailing list