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

Python abc module: Extending both an abstract base class and an exception-derived class leads to surprising behavior

扩展一个抽象基类和一个从"object"派生的类可以如您所期望的那样工作:如果您还没有实现所有的抽象方法和属性,您会得到一个错误。

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

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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."

…产量…

1
2
3
4
5
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

我知道"exception"和"mycustomerexception"没有属性"foo",那么为什么我不去实例化"mycustomerexception"?

编辑:作为记录,这是我最终使用的黑客解决方案。不是真正的等价物,但为我的目的而工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
#"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."

看起来这是因为BaseException__new__方法不关心抽象方法/属性。

当您试图实例化myConcreteClass_1时,它最终从Exception类调用__new__。当需要实例化myConcreteClass_2时,从object调用__new__

1
2
3
4
5
6
7
8
>>> 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

Exception类不提供__new__方法,但它的父级BaseException提供了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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;
}

object__new__实施相比:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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);
}

如您所见,当抽象方法未被重写时,object.__new__有代码来抛出错误,但BaseException.__new__没有。