[Python-3000] removing exception .args

Nick Coghlan ncoghlan at gmail.com
Sat Jul 21 20:02:57 CEST 2007


Andrew Dalke wrote:
> Having support for a single object (almost always a
> string) passed into the exception is pragmatically useful,
> so I think the base exception class should look like
> 
> class Exception(object):
>    msg = None
>    def __init__(self, msg):
>      self.msg = msg
>    def __str__(self):
>      if self.msg is not None:
>        return "%s()" % (self.__class__.__name__,)
>      else:
>        return "%s(%r)" % (self.__class__.__name__, self.msg)
> 
> **

Went there, didn't like it, left again. See PEP 352, especially the 
section on the late (unlamented) BaseException.message.

> The rest of this email is because I'm detail oriented
> and present evidence to back up my assertion.
> 
> There are a number of subclasses which should but don't
> call the base __init__, generic error reporting software
> can't use the "args protocol" for anything.  Pretty much
> the only thing a generic error report mechanism (like
> traceback and logging) can do is call str() on the exception.

As of Python 2.5, you can rely on the attribute being present, as it is 
provided automatically by BaseException:

.>>> class MyException(Exception):
...   def __init__(self):
...     pass
...
.>>> MyException().args
()

Of course, as Guido pointed out, args will be empty unless the exception 
sets it directly or via BaseException.__init__.

> This is correct, but cumbersome.  Why should we
> encourage all non-trivial subclasses to look like this?

If you want to avoid requiring that subclasses call your __init__ 
method, you can actually do that by putting any essential initialisation 
into the __new__ method instead. Then the requirement is merely to call 
the parent __new__ method if you override __new__, and you have to do 
something like that in order to create the class instance in the first 
place.

To rewrite the example from getopt using this technique:

class GetoptError(Exception):
     def __new__(cls, msg, opt=''):
         self = super(cls, GetoptError).__new__(cls, msg, opt='')
         self.msg = msg
         self.opt = opt
         return self

     def __str__(self):
         return self.msg

I actually find using __new__ this way to be a useful practice in 
general for setting up class invariants in base classes, as it's easy to 
forget to call __init__ on the base class, but forgetting to call 
__new__ takes some serious effort. Putting the essential parts in 
__new__ means never having to include the instruction that "you must 
call this classes __init__ method when subclassing and overriding 
__init__" into any API documentation I write.

Regards,
Nick.

-- 
Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia
---------------------------------------------------------------
             http://www.boredomandlaziness.org


More information about the Python-3000 mailing list