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',)