[Python-ideas] Arguments to exceptions

Jeff Walker jeff.walker00 at yandex.com
Fri Jul 7 00:37:57 EDT 2017


06.07.2017, 13:00, "Steven D'Aprano" <steve at pearwood.info>:
> 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
> expects:
>
> - 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.

I am not understanding what is bad about your example. Yes, BaseException would allow an
arbitrary set of arguments to be passed to the exception. It also defines how they would be
handled by default. If people did not want that, they could subclass BaseException and
replace the constructor and the __str__ method. With Ken's proposal, NameError and
IndexError could then be replaced with:

    class NameError(Exception):
        '''Name not found.

        :param *args:
            args[0] contains the name that was not found. Values passed in args[1:] are ignored.
        :param **kwargs:
            kwargs['template'] is format string used to assemble an error message. This strings
            format() method is called with *args and **kwargs as arguments, and the result is
            returned by __str__().

        args and kwargs are saved as attributes, so you can access the exception's arguments
        through them.
        '''
        pass


    class IndexError(Exception):
        '''Sequence index out of range.

        :param *args:
            args[0] contains the value of the index.
        :param **kwargs:
            kwargs['template'] is format string used to assemble an error message. This strings
            format() method is called with *args and **kwargs as arguments, and the result is
            returned by __str__().

            args and kwargs are saved as attributes, so you can access the exception's arguments
            through them.
        '''
        pass

OSError could be implemented by overriding the __str__() method.

Once this is done, the new versions do everything the old versions do, but provide access to
the components of the error message. The are also discoverable, more discoverable than the
originals.

In addition, they are easily extensible. For example, if I raise the NameError myself, I can
provide additional useful information:

    try:
        raise NameError('welker', db='user')
    except NameError as e:
        db = e.kwargs.get('db')
        print('{}: {} not found.'.format(e.args[0], db) if db else str(e))

If, as you suggest, some one writes:

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

it will work as expected as long as they confined themselves to using str(e). It would only fail
if someone directly tried to access the argument using e.args, but that would be a very
unusual thing to do and the issue could be worked around with by examining args and kwargs.

Jeff






More information about the Python-ideas mailing list