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

Posted

技术标签:

【中文标题】python abstractmethod 与另一个基类破坏了抽象功能【英文标题】:python abstractmethod with another baseclass breaks abstract functionality 【发布时间】: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 :) 哦,见鬼 - 如果有人进入源代码,我会很高兴。但这是规范的一部分吗?是否存在需要这种行为的情况?或者它是一个错误,或者只是没有被考虑的案例? 【参考方案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 methodsuffix 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。

【讨论】:

以上是关于python abstractmethod 与另一个基类破坏了抽象功能的主要内容,如果未能解决你的问题,请参考以下文章

在python中,我如何对一列中每个值与另一列中的值发生的次数(多少行)建立矩阵?

Python2和Python3中@abstractmethod的用法

python基础-abstractmethod__属性propertysetterdeleterclassmethodstaticmethod

将视频帧与另一个图像python进行比较? [关闭]

Python面向对象编程——一些类定义(杂)

python - Pandas - FillNa 与另一个具有相似列的非空行