与另一个基类的python abstractmethod打破了抽象功能

时间:2016-05-23 19:24:44

标签: python multiple-inheritance metaclass abstract-methods

考虑以下代码示例

import abc
class ABCtest(abc.ABC):
    @abc.abstractmethod
    def foo(self):
        raise RuntimeError("Abstract method was called, this should be impossible")

class ABCtest_B(ABCtest):
    pass

test = ABCtest_B()

这正确地引发了错误:

Traceback (most recent call last):
  File "/.../test.py", line 10, in <module>
    test = ABCtest_B()
TypeError: Can't instantiate abstract class ABCtest_B with abstract methods foo

但是,当ABCtest的子类也继承自strlist等内置类型时,没有错误,test.foo()调用抽象方法:

class ABCtest_C(ABCtest, str):
    pass

>>> test = ABCtest_C()
>>> test.foo()
Traceback (most recent call last):
  File "<pyshell#0>", line 1, in <module>
    test.foo()
  File "/.../test.py", line 5, in foo
    raise RuntimeError("Abstract method was called, this should be impossible")
RuntimeError: Abstract method was called, this should be impossible

当继承自C中定义的任何类(包括itertools.chainnumpy.ndarray但仍然正确地引发python中定义的类的错误时,似乎会发生这种情况。为什么实现其中一个内置类型会破坏抽象类的功能?

2 个答案:

答案 0 :(得分:7)

令人惊讶的是,阻止实例化抽象类的测试发生在object.__new__中,而不是由abc模块本身定义的任何内容:

static PyObject *
object_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    ...
    if (type->tp_flags & Py_TPFLAGS_IS_ABSTRACT) {
        ...
        PyErr_Format(PyExc_TypeError,
                     "Can't instantiate abstract class %s "
                     "with abstract methods %U",
                     type->tp_name,
                     joined);

(差不多?)所有不是object的内置类型提供的不同__new__覆盖object.__new__ 而不会调用object.__new__ 。当您从非object内置类型多重继承时,继承其__new__方法,绕过抽象方法检查。

我在__new__文档中没有看到abc或内置类型的多重继承。文档可以在这里使用增强功能。

他们使用元类进行ABC实现似乎有些奇怪,将其他元类与抽象类一起使用变得一团糟,然后将核心语言代码中的关键检查与{{无关1}}并运行抽象和非抽象类。

问题跟踪器上的这个问题有report,自2009年以来一直在萎缩。

答案 1 :(得分:4)

我问了一个类似的question,并根据user2357112 supports Monica的链接错误报告,提出了一种解决方法(基于Xiang Zhang的建议):

from abc import ABC, abstractmethod

class Base(ABC):
    @abstractmethod
    def foo(self):
        pass

    @abstractmethod
    def bar(self):
        pass

    def __new__(cls, *args, **kwargs):
        abstractmethods = getattr(cls, '__abstractmethods__', None)
        if abstractmethods:
            msg = "Can't instantiate abstract class {name} with abstract method{suffix} {methods}"
            suffix = 's' if len(abstractmethods) > 1 else ''
            raise TypeError(msg.format(name=cls.__name__, suffix=suffix, methods=', '.join(abstractmethods)))
        return super().__new__(cls, *args, **kwargs)

class Derived(Base, tuple):
    pass

Derived()

这引发了TypeError: Can't instantiate abstract class Derived with abstract methods bar, foo,这是原始行为。