13

考虑以下代码示例

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 中定义的类的错误。为什么实现一种内置类型会破坏抽象类的功能?

4

2 回答 2

15

令人惊讶的是,阻止实例化抽象类的测试发生在 中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 实现使用元类,这似乎有点奇怪,将其他元类与抽象类一起使用会变得一团糟,然后将关键检查放在与抽象类无关的核心语言代码中abc并为这两个抽象类运行和非抽象类。

自 2009 年以来一直在萎靡不振的问题跟踪器上有一份关于此问题的报告。

于 2016-05-23T22:25:08.913 回答
4

我问了一个类似的问题,基于user2357112 支持 Monica的链接错误报告,我想出了这个解决方法(基于Zhang 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,这是原始行为。

于 2020-02-08T09:16:10.233 回答