0

Can someone tell me why below codes output ex? Many thanks!


class Ex(Exception):

    def __init__(self,msg):
        Exception.__init__(self,msg+msg)
        self.args = (msg,)

try:
    raise Ex("ex")
except Ex as e:
    print(e)
except Exception as e:
    print(e)
mi__
  • 11
  • 2
  • 1
    Because you set your exception's `args` to `(msg,)` and the `msg` you specified is `"ex"`. Is that the part you meant? Or are you asking how `try/raise/except` works? – khelwood May 22 '20 at 10:52

1 Answers1

2

The Exception constructor assigns all arguments to the field self.args:

>>> Exception('ex').args
('ex',)

And the value of self.args is used when converting the exception to a string:

>>> str(Exception('ex'))
'ex'

So after calling Exception.__init__(self,msg+msg), you will have self.args == (msg+msg,), and you would see exex if you print it at that point. But the next line overwrites that again with self.args = (msg,).


args is supposed to be a tuple or other iterable. You normally don't need to mess with it or even know it exists, because you can just pass it as arguments to the constructor:

[Built-in exceptions] have an “associated value” indicating the detailed cause of the error. This may be a string or a tuple of several items of information (e.g., an error code and a string explaining the code). The associated value is usually passed as arguments to the exception class’s constructor.

The docs for BaseException say:

If str() is called on an instance of this class, the representation of the argument(s) to the instance are returned, or the empty string when there were no arguments.

This would suggest that it simply calls repr(self.args), but it's actually str(e.args) (see below). This still means that repr is applied to each element of the iterable separately.

It also seems that an exception (no pun intended) is made for the case len(self.args) == 1: in this case it returns str(self.args[0]) instead.

Looking it up in the source code of CPython confirms this:

static PyObject *
BaseException_str(PyBaseExceptionObject *self)
{
    switch (PyTuple_GET_SIZE(self->args)) {
    case 0:
        return PyUnicode_FromString("");
    case 1:
        return PyObject_Str(PyTuple_GET_ITEM(self->args, 0));
    default:
        return PyObject_Str(self->args);
    }
}

The args field must be a tuple, and if you assign something else, it's forcibly converted into a tuple:

>>> e = Exception()
>>> e.args = 'ex'
>>> e.args
('e', 'x')
Thomas
  • 174,939
  • 50
  • 355
  • 478
  • Thanks so much for your reply! Sorry for one more question, why the value of self.args is used when converting the exception to a string? I changed self.args = msg and it outputs ('e', 'x'), why not ex? Thanks! – mi__ May 22 '20 at 11:34
  • @mi__ `args` is supposed to be a tuple or other iterable. A `str` like `'ex'` happens to be iterable. I've added a fairly extensive edit. – Thomas May 23 '20 at 09:47