Skip to content

Commit fcfe80e

Browse files
Nateserhiy-storchaka
Nate
authored andcommitted
bpo-29822: Make inspect.isabstract() work during __init_subclass__. (python#678)
At the time when an abstract base class' __init_subclass__ runs, ABCMeta.__new__ has not yet finished running, so in the presence of __init_subclass__, inspect.isabstract() can no longer depend only on TPFLAGS_IS_ABSTRACT.
1 parent 2e576f5 commit fcfe80e

File tree

3 files changed

+49
-1
lines changed

3 files changed

+49
-1
lines changed

Lib/inspect.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
__author__ = ('Ka-Ping Yee <ping@lfw.org>',
3232
'Yury Selivanov <yselivanov@sprymix.com>')
3333

34+
import abc
3435
import ast
3536
import dis
3637
import collections.abc
@@ -291,7 +292,27 @@ def isroutine(object):
291292

292293
def isabstract(object):
293294
"""Return true if the object is an abstract base class (ABC)."""
294-
return bool(isinstance(object, type) and object.__flags__ & TPFLAGS_IS_ABSTRACT)
295+
if not isinstance(object, type):
296+
return False
297+
if object.__flags__ & TPFLAGS_IS_ABSTRACT:
298+
return True
299+
if not issubclass(type(object), abc.ABCMeta):
300+
return False
301+
if hasattr(object, '__abstractmethods__'):
302+
# It looks like ABCMeta.__new__ has finished running;
303+
# TPFLAGS_IS_ABSTRACT should have been accurate.
304+
return False
305+
# It looks like ABCMeta.__new__ has not finished running yet; we're
306+
# probably in __init_subclass__. We'll look for abstractmethods manually.
307+
for name, value in object.__dict__.items():
308+
if getattr(value, "__isabstractmethod__", False):
309+
return True
310+
for base in object.__bases__:
311+
for name in getattr(base, "__abstractmethods__", ()):
312+
value = getattr(object, name, None)
313+
if getattr(value, "__isabstractmethod__", False):
314+
return True
315+
return False
295316

296317
def getmembers(object, predicate=None):
297318
"""Return all members of an object as (name, value) pairs sorted by name.

Lib/test/test_inspect.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,30 @@ def foo(self):
229229
self.assertFalse(inspect.isabstract(int))
230230
self.assertFalse(inspect.isabstract(5))
231231

232+
def test_isabstract_during_init_subclass(self):
233+
from abc import ABCMeta, abstractmethod
234+
isabstract_checks = []
235+
class AbstractChecker(metaclass=ABCMeta):
236+
def __init_subclass__(cls):
237+
isabstract_checks.append(inspect.isabstract(cls))
238+
class AbstractClassExample(AbstractChecker):
239+
@abstractmethod
240+
def foo(self):
241+
pass
242+
class ClassExample(AbstractClassExample):
243+
def foo(self):
244+
pass
245+
self.assertEqual(isabstract_checks, [True, False])
246+
247+
isabstract_checks.clear()
248+
class AbstractChild(AbstractClassExample):
249+
pass
250+
class AbstractGrandchild(AbstractChild):
251+
pass
252+
class ConcreteGrandchild(ClassExample):
253+
pass
254+
self.assertEqual(isabstract_checks, [True, True, False])
255+
232256

233257
class TestInterpreterStack(IsTestBase):
234258
def __init__(self, *args, **kwargs):

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,9 @@ Extension Modules
317317
Library
318318
-------
319319

320+
- bpo-29822: inspect.isabstract() now works during __init_subclass__. Patch
321+
by Nate Soares.
322+
320323
- bpo-29960: Preserve generator state when _random.Random.setstate()
321324
raises an exception. Patch by Bryan Olson.
322325

0 commit comments

Comments
 (0)