将关键字参数传递给自定义异常 - 异常

Passing keyword arguments to custom exceptions - anomaly

为什么我得到以下两个代码片段的不同结果 (Python 3.4):

class MainError(Exception):
    def __init__(self, msg, **parms):
        super().__init__()  
        self.msg = msg
        self.parms = parms
        print('Parms original', parms)

    def __str__(self):
        return self.msg + ':' + str(self.parms)

class SubError(MainError):
    def __init__(self, msg, **parms):
        super().__init__(msg, **parms)

try:
    raise SubError('Error occured', line = 22, col = 11)
except MainError as e:
    print(e)


>>> 
Parms original {'line': 22, 'col': 11}
Error occured:{'line': 22, 'col': 11}

并且:

class MainError(Exception):
    def __init__(self, msg, **args):
        super().__init__()  
        self.msg = msg
        self.args = args
        print('Parms original', args)

    def __str__(self):
        return self.msg + ':' + str(self.args)

class SubError(MainError):
    def __init__(self, msg, **args):
        super().__init__(msg, **args)

try:
    raise SubError('Error occured', line = 22, col = 11)
except MainError as e:
    print(e)


>>> 
Parms original {'line': 22, 'col': 11}
Error occured:('line', 'col')

这是因为错误参数被覆盖,通过将它们转换为 C 级别的 Python 元组。

这是 Python 的 BaseException class: https://hg.python.org/cpython/file/tip/Objects/exceptions.c

从第 31 行开始,我们看到以下内容:

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;
}

同样,init 调用具有相同的元组转换:

BaseException_init(PyBaseExceptionObject *self, PyObject *args, PyObject *kwds)
{
    PyObject *tmp;

    if (!_PyArg_NoKeywords(Py_TYPE(self)->tp_name, kwds))
        return -1;

    tmp = self->args;
    self->args = args;
    Py_INCREF(self->args);
    Py_XDECREF(tmp);

    return 0;
}

简而言之,self.args 正在转换为一个元组,该元组又被转换回一个字符串,这导致了差异。

BaseException class 被(我相信)所有方法包装器调用为必需参数。

如果给它传递一个不可迭代的参数(例如整数),这是很可观的:

>>> class CustomException(Exception):
...     def __init__(self):
...         super(CustomException, self).__init__('a')
...         self.args = 1
...     def __repr__(self):
...         print(self.args)
...         return ''
... 
>>> CustomException()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in __init__
TypeError: 'int' object is not iterable

这个故事的寓意:不要给你的变量命名,因为这些词经常被重新定义,并且是你正在使用的 class 的关键术语。