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

IAR*_*ARI 7 python metaclass multiple-inheritance 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()
Run Code Online (Sandbox Code Playgroud)

这正确地引发了错误:

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

但是当子类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
Run Code Online (Sandbox Code Playgroud)

这似乎从C中定义的任何类继承包括何时发生itertools.chainnumpy.ndarray,但仍引起了正确使用Python定义类的错误.为什么实现其中一个内置类型会破坏抽象类的功能?

use*_*ica 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);
Run Code Online (Sandbox Code Playgroud)

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

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

他们使用元类用于ABC实现,使用其他元类与抽象类一样混乱,然后将核心语言代码中的关键检查与之无关,abc并运行两个抽象类似乎有点奇怪.和非抽象类.

关于问题跟踪器上的这个问题的报告自2009年以来一直在萎缩.


Aar*_*ron 5

我问了一个类似的问题,并基于user2357112 支持 Monica的链接错误报告,我想出了这个解决方法(基于张翔的建议):

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

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