Python abc模块:扩展抽象基类和异常派生类会导致令人惊讶的行为

Gra*_* F. 7 python abstract-class exception multiple-inheritance abc

扩展抽象基类和从"对象"派生的类可以正常工作:如果您尚未实现所有抽象方法和属性,则会出现错误.

奇怪的是,用扩展"Exception"的类替换对象派生类允许您创建不实现所有必需的抽象方法和属性的类的实例.

例如:

import abc

# The superclasses
class myABC( object ):
    __metaclass__ = abc.ABCMeta

    @abc.abstractproperty
    def foo(self):
        pass

class myCustomException( Exception ):
    pass

class myObjectDerivedClass( object ):
    pass

# Mix them in different ways
class myConcreteClass_1(myCustomException, myABC):
    pass

class myConcreteClass_2(myObjectDerivedClass, myABC):
    pass

# Get surprising results
if __name__=='__main__':
    a = myConcreteClass_1()
    print "First instantiation done. We shouldn't get this far, but we do."
    b = myConcreteClass_2()
    print "Second instantiation done. We never reach here, which is good."
Run Code Online (Sandbox Code Playgroud)

... ...产量

First instantiation done. We shouldn't get this far, but we do.
Traceback (most recent call last):
  File "C:/Users/grahamf/PycharmProjects/mss/Modules/mssdevice/sutter/sutter/test.py", line 28, in <module>
    b = myConcreteClass_2()
TypeError: Can't instantiate abstract class myConcreteClass_2 with abstract methods foo
Run Code Online (Sandbox Code Playgroud)

我知道"Exception"因此"myCustomException"没有属性"foo",所以为什么我要实例化"myCustomException"?

编辑:为了记录,这是我最终进入的hackish变通方法.不是真正等同,但适用于我的目的.

# "abstract" base class
class MyBaseClass( Exception ):
    def __init__(self):
        if not hasattr(self, 'foo'):
            raise NotImplementedError("Please implement abstract property foo")


class MyConcreteClass( MyBaseClass ):
    pass

if __name__=='__main__':
    a = MyConcreteClass()
    print "We never reach here, which is good."
Run Code Online (Sandbox Code Playgroud)

dan*_*ano 6

看起来这是因为__new__for 的方法BaseException不在乎抽象方法/属性。

当您尝试实例化时myConcreteClass_1,它最终__new__Exception类中调用。当要实例化时myConcreteClass_2,它调用__new__from object

>>> what.myConcreteClass_1.__new__()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: exceptions.Exception.__new__(): not enough arguments
>>> what.myConcreteClass_2.__new__()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: object.__new__(): not enough arguments
Run Code Online (Sandbox Code Playgroud)

Exception类没有提供一个__new__方法,但它的母公司BaseException,做

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;
}
Run Code Online (Sandbox Code Playgroud)

将此与以下__new__实现object比较:

static PyObject *
object_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    if (excess_args(args, kwds) &&
        (type->tp_init == object_init || type->tp_new != object_new)) {
        PyErr_SetString(PyExc_TypeError, "object() takes no parameters");
        return NULL;
    }

    if (type->tp_flags & Py_TPFLAGS_IS_ABSTRACT) {
        PyObject *abstract_methods = NULL;
        PyObject *builtins;
        PyObject *sorted;
        PyObject *sorted_methods = NULL;
        PyObject *joined = NULL;
        PyObject *comma;
        _Py_static_string(comma_id, ", ");
        _Py_IDENTIFIER(sorted);

        /* Compute ", ".join(sorted(type.__abstractmethods__))
           into joined. */
        abstract_methods = type_abstractmethods(type, NULL);
        if (abstract_methods == NULL)
            goto error;
        builtins = PyEval_GetBuiltins();
        if (builtins == NULL)
            goto error;
        sorted = _PyDict_GetItemId(builtins, &PyId_sorted);
        if (sorted == NULL)
            goto error;
        sorted_methods = PyObject_CallFunctionObjArgs(sorted,
                                                      abstract_methods,
                                                      NULL);
        if (sorted_methods == NULL)
            goto error;
        comma = _PyUnicode_FromId(&comma_id);
        if (comma == NULL)
            goto error;
        joined = PyUnicode_Join(comma, sorted_methods);
        if (joined == NULL)
            goto error;

        PyErr_Format(PyExc_TypeError,
                     "Can't instantiate abstract class %s "
                     "with abstract methods %U",
                     type->tp_name,
                     joined);
    error:
        Py_XDECREF(joined);
        Py_XDECREF(sorted_methods);
        Py_XDECREF(abstract_methods);
        return NULL;
    }
    return type->tp_alloc(type, 0);
}
Run Code Online (Sandbox Code Playgroud)

如您所见object.__new__,当存在未被覆盖但未被覆盖的抽象方法时,有代码会引发错误BaseException.__new__

  • 与此问题相关的人员的相关主题:[此处](http://stackoverflow.com/questions/2862153/python-2-6-3-abstract-base-class-misunderstanding/2862419#2862419)和[此处](http ://stackoverflow.com/questions/20107530/can-one-declare-an-abstract-exception-in-python) (2认同)