Home > OS >  Python subclassing Exceptions
Python subclassing Exceptions

Time:12-05

I've come across some behavior in Python 3.7 that I don't understand when subclassing Exception

I've been using Python for a long time and I've always had the understanding that base class constructors are not implicit and must be called explicitly.

For example, the below behavior is consistent with my understanding, since the constructor for A is never called x will not be defined.

class A():
    def __init__(self, x):
        self.x = x

class B(A):
    def __init__(self, x):
        pass

b = B(1)
print(b.x)

AttributeError: 'B' object has no attribute 'x'

But in the below example, I don't understand how the Exception base class receives the message hello when I don't explicitly provide it to the base class constructor.

class E(Exception):
    def __init__(self, msg):
        # don't do anything with msg
        pass

raise E("hello")

__main__.E: hello

CodePudding user response:

I believe the Exception class has a special __new__() function to keep track of the arguments that was passed in. In Python 3.6.10, I tried ignoring the first argument passed into __new__(), and it doesn't show the argument passed in.

class E2(Exception):
    def __new__(cls, msg, *args, **kwargs):
        # ignore the msg argument passed in
        return super().__new__(cls, *args, **kwargs)
    def __init__(self, msg):
        # also ignore the msg passed in
        pass

raise E2("hello")

This prints an error message without the "hello"

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
__main__.E2

CodePudding user response:

By looking at the source code, BaseException class have a BaseException_new implementation(Equivalent to __new__ in python layer) which binds all the arguments to the exception instance during the creation.

static PyObject *
BaseException_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    PyBaseExceptionObject *self;

    self = (PyBaseExceptionObject *)type->tp_alloc(type, 0);
    if (!self)
        return NULL;
    /* the dict is created on the fly in PyObject_GenericSetAttr */
    self->dict = NULL;
    self->traceback = self->cause = self->context = NULL;
    self->suppress_context = 0;

    if (args) {
        self->args = args;
        Py_INCREF(args);
        return (PyObject *)self;
    }

    self->args = PyTuple_New(0);
    if (!self->args) {
        Py_DECREF(self);
        return NULL;
    }

    return (PyObject *)self;
}

You can confirm this is in python layer.

>>> class E(Exception):
...     def __init__(self, msg):
...         pass
...
>>>
>>> a = E("hello")
>>> a.args
('hello',)
  • Related