9

Suppose I define the following exception:

>>> class MyError(Exception):
...     def __init__(self, arg1):
...         pass

Then I instantiate the class to create an exception object:

>>> e = MyError('abc')
>>> e.args
('abc',)

Here how is the args attribute getting set? (In the __init__, I am doing nothing.)

bakatrouble
  • 1,746
  • 13
  • 19
debashish
  • 1,374
  • 1
  • 19
  • 29
  • This attribute is described in the docs: https://docs.python.org/3.6/library/exceptions.html#BaseException.args It doesn't answer the question why is it assigned even with overwritten constructor though – bakatrouble Jun 28 '17 at 10:37

2 Answers2

9

args is implemented as a data descriptor with __get__ and __set__ methods.

This takes place inside BaseException.__new__ like @bakatrouble mentioned. Among other things, what happens inside BaseException.__new__ is roughly like the Python code below:

class BaseException:
    def __new__(cls, *args): 
        # self = create object of type cls
        self.args = args  # This calls: BaseException.args.__set__(self, args) 
        ...
        return self

In C code of Python 3.7.0 alpha 1, the above Python code looks like this (inspect Python's C code for any past or future differences):

BaseException_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    # other things omitted... 
    self = (PyBaseExceptionObject *)type->tp_alloc(type, 0);
    # many things follow... 
    if (args) {
        self->args = args;
        Py_INCREF(args);
        return (PyObject *)self;

    }
    # many more things follow
}

Experimenting interactively:

>>> e = Exception('aaa')
>>> e
Exception('aaa',)

>>> BaseException.args.__set__(e, ('bbb',))
>>> e
Exception('bbb',)
>>> BaseException.args.__get__(e)
('bbb',)

Hence, the magical inspiration of args that makes your eyes look heavenward takes place in BaseException.__new__, when an object of BaseException or any of its sub-classes is created.

GIZ
  • 4,409
  • 1
  • 24
  • 43
  • The `Exception` class also has `__new__` method. Going by the inheritance hierarchy, shouldn't this method be called instead of `BaseException.__new__`? – debashish Jun 28 '17 at 14:27
  • @Deb Essentially all exception classes use the same methods of `BaseException` including `__init__` and `__new__` . `Exception` extends `BaseException` and if you look at the [C source code](https://github.com/python/cpython/blob/master/Objects/exceptions.c#L452) specifically inside the macro you'll notice that the macro `SimpleExtendsException` uses both`BaseException_new` and `BaseException_init`. Since I'm not a C wizard, I welcome any verification or correction from other members. But to me, this is obviously the case with 99% certainty. – GIZ Jun 28 '17 at 16:26
  • @Deb Note if you check `BaseException.__dict__['__new__'] is Exception.__dict__['__new__']` in Python, this is `False`, but they could be the same! Just like the case for [`os.unlik` and `os.remove`](https://stackoverflow.com/questions/27241091/in-python-2-7-why-is-os-remove-not-identical-to-os-unlink). – GIZ Jun 28 '17 at 16:32
1

It is being set in the BaseException.__new__() method, which could be seen here: source code

Note: in Python 2.7 it is being set in the BaseException.__init__() method, so the override makes .args dict always empty (not sure if pointing to the correct line): source code

bakatrouble
  • 1,746
  • 13
  • 19
  • @Deb when you input `e = MyError('abc')`, the inheritance translates `Exception('abc')` – PRMoureu Jun 28 '17 at 10:51
  • @PRMoureu No, superclass constructor should be called explicitely if overwritten. For that reason it doesn't work in Python 2.7 because `args` attribute is being set in the constructor, not in the `__new__()` method. – bakatrouble Jun 28 '17 at 10:55
  • @PRMoureu In this aspect behavior is the same. If you want overwritten superclass method to be executed, you should call it explicitely. But the `__new__()` method has some magic in it which I don't fully understand yet... https://docs.python.org/3.6/reference/datamodel.html#object.__new__ – bakatrouble Jun 28 '17 at 11:08