【问题标题】:python abstractmethod with another baseclass breaks abstract functionalitypython abstractmethod 与另一个基类破坏了抽象功能
【发布时间】:2016-09-20 19:18:54
【问题描述】:

考虑以下代码示例

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

【问题讨论】:

  • @DonkeyKong(或其他任何不明白的人)方法foo 应该被强制在子类中被覆盖,通常(并且不从str 继承)实例化它会引发一个错误,但是当也从 str 继承时不会发生错误,并且抽象方法 test.foo 是一个有效的可调用方法。
  • @TadhgMcDonald-Jensen 刚刚了解,谢谢 :)
  • @Torxed str 不是变量名。
  • 我才意识到,谢谢你让我读了两遍@DonkeyKong :)
  • 哦,见鬼 - 如果有人进入源代码,我会很高兴。但这是规范的一部分吗?是否存在需要这种行为的情况?或者它是一个错误,或者只是没有被考虑的案例?

标签: python multiple-inheritance metaclass abstract-methods


【解决方案1】:

我问了一个类似的question 并根据user2357112 supports Monicas 链接的错误报告,我想出了这个解决方法(基于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,这是原始行为。

【讨论】:

    【解决方案2】:

    令人惊讶的是,阻止实例化抽象类的测试发生在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__ 方法,绕过抽象方法检查。

    我在abc 文档中看不到任何关于__new__ 或内置类型的多重继承的信息。文档可以在此处使用增强功能。

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

    自 2009 年以来一直处于低迷状态的问题跟踪器上有此问题的 report

    【讨论】:

      猜你喜欢
      • 2011-10-08
      • 2011-06-23
      • 2021-06-11
      • 1970-01-01
      • 2012-04-03
      • 1970-01-01
      相关资源
      最近更新 更多