it appears inspect.isabstract
will only return true if BOTH the metaclass
AND @abstractmethod
are present. But this is not what the doc says, at least as I read it - and more importantly I don't believe it's Least Surprise at all. Do I have this correct / bug needed?
this class evaluates as abstract as expected
class Both(ABC):
isabstract_result = True
@abstractmethod
def foo(self):
pass
the result of isabstract()
here
is consistent with neither the docs NOR common usage
https://docs.python.org/3/library/abc.html
class JustABC(ABC):
# docs: "an abstract base class can be created by simply deriving from ABC"
isabstract_result = False
I suppose the technical argument here is that in order for derived classes to become concrete, they'd need to implement a (previously abstractmethod
) method; thus by induction a base class without such a method in its mro
must not be abstract
- but the IMHO this would be a documentation bugfix need, as it doesn't seem Least Surprise especially given the explicit phrasing as it exists today.
Here's runnable code showing the above (and a couple other results of the above). The unittest.Testcase
passes when run on pythong 3.8.5 (ubuntu 20.04 default)
from unittest import TestCase
from abc import abstractmethod, ABC
from inspect import isabstract
# these classes evaluate as abstract as expected
class Both(ABC):
isabstract_result = True
@abstractmethod
def foo(self):
pass
# the result of isabstract() here
# is consistent with neither the docs NOR common usage
# https://docs.python.org/3/library/abc.html
class JustABC(ABC):
# docs: "an abstract base class can be created by simply deriving from ABC"
isabstract_result = False
# that this code loads without error would seem to be inconsistent with the docs
# although admittedly nothing about "required" implies there is any error checking
class JustABm:
isabstract_result = False
# docs: Using this decorator requires that the class [...] or is derived from [ABC]"
@abstractmethod
def foo(self):
pass
# for reference, trivial derrived classes preserve the above
class ChildBoth(Both):
isabstract_result = True
class ChildJustABm(JustABC):
isabstract_result = False
class ChildJustABC(JustABm, ABC):
isabstract_result = False
# and there is asymitry about adding the other abc notion;
# although this probably makes sense given the implementation & inheritance
class ChildAddABm(JustABC):
isabstract_result = True
@abstractmethod
def foo(self):
pass
class ChildAddABC(JustABm, ABC):
isabstract_result = False
CLASSES = (
JustABC,
JustABm,
Both,
ChildJustABC,
ChildJustABm,
ChildBoth,
ChildAddABm,
ChildAddABC,
)
class test_isabstract(TestCase):
""" verify the flags above: test passes """
def test_isabstract(self):
for cls, status in zip(CLASSES, map(isabstract, CLASSES)):
self.assertEqual(
cls.isabstract_result,
status,
f"error, {cls.__name__ = } {'IS' if status else 'is NOT'} abs, which is not expected.",
)
def test_runtime_matches_isabstract(self):
# at least the actual runtime behavior is consistent with inspect.isabstract
for cls in CLASSES:
try:
cls()
self.assertFalse(
cls.isabstract_result,
f"{cls} instantiated, but should have failed as ABC",
)
except TypeError as _ex:
self.assertTrue(
cls.isabstract_result,
f"{cls} failed to instantiate as abc unexpectedly",
)
question from:
https://stackoverflow.com/questions/65647468/python-inspect-isabstract-incorrect-result-per-abc-docs 与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…