python abstractmethod with another baseclass breaks abstract functionality
考虑下面的代码示例
1 2 3 4 5 6 7 8 9 10 | 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() |
这正确地引发了错误:
1 2 3 4 | 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 |
但是,当
1 2 3 4 5 6 7 8 9 10 11 | 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中定义的任何类(包括
令人惊讶的是,阻止实例化抽象类的测试发生在
1 2 3 4 5 6 7 8 9 10 11 | 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); |
(几乎?)不是
我没有看到关于
他们将一个元类用于ABC实现似乎很奇怪,将其他元类与抽象类一起使用,然后将关键的检查放入与
自2009年以来,一直存在有关问题跟踪器的此问题的报告。
我问了一个类似的问题,并且基于user2357112支持Monicas链接的bug报告,我想出了以下解决方法(基于来自Xiang Zhang的建议):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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() |
这将引发