bpo-29822: make inspect.isabstract() work during __init_subclass__#678
bpo-29822: make inspect.isabstract() work during __init_subclass__#678serhiy-storchaka merged 10 commits intopython:masterfrom
Conversation
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.
|
@Soares, thanks for your PR! By analyzing the history of the files in this pull request, we identified @zestyping, @1st1 and @larryhastings to be potential reviewers. |
Lib/inspect.py
Outdated
| def isabstract(object): | ||
| """Return true if the object is an abstract base class (ABC).""" | ||
| return bool(isinstance(object, type) and object.__flags__ & TPFLAGS_IS_ABSTRACT) | ||
| if isinstance(object, type): |
There was a problem hiding this comment.
if isinstance(object, type):
return FalseThis will decrease an indentation level.
Lib/inspect.py
Outdated
| def isabstract(object): | ||
| """Return true if the object is an abstract base class (ABC).""" | ||
| return bool(isinstance(object, type) and object.__flags__ & TPFLAGS_IS_ABSTRACT) | ||
| if isinstance(object, type): |
There was a problem hiding this comment.
if not isinstance(object, type):
return FalseThis will decrease an indentation level.
Lib/inspect.py
Outdated
| if isinstance(object, type): | ||
| if object.__flags__ & TPFLAGS_IS_ABSTRACT: | ||
| return True | ||
| elif (issubclass(type(object), abc.ABCMeta) |
There was a problem hiding this comment.
Just if rather than elif. And you can return False if the condition is false for decreasing an indentation level.
Lib/inspect.py
Outdated
| # We're likely in the __init_subclass__ of an ABC. ABCMeta.__new__ | ||
| # hasn't finished running yet. We have to search for abstractmethods | ||
| # by hand. Code copied from ABCMeta.__new__. | ||
| abstracts = {name |
There was a problem hiding this comment.
It seems to me that collecting names of abstract method is not needed.
False can be returned just after finding any abstract method.
- return earlier to decrease indentation levels - return immediately upon finding an abstractmethod, instead of collecting the whole set
Lib/inspect.py
Outdated
| if getattr(value, "__isabstractmethod__", False): | ||
| return True | ||
| for base in object.__bases__: | ||
| for name in getattr(base, "__abstractmethods__", frozenset()): |
There was a problem hiding this comment.
You can use an empty tuple () rather than frozenset(). This is shorter and faster.
| def foo(self): | ||
| pass | ||
|
|
||
| class ClassExample(AbstractClassExample): |
There was a problem hiding this comment.
The test doesn't cover the case when the abstract method is not in the __dict__ of final class, but is inherited from base class.
Misc/NEWS
Outdated
| - Issue #29581: ABCMeta.__new__ now accepts **kwargs, allowing abstract base | ||
| classes to use keyword parameters in __init_subclass__. Patch by Nate Soares. | ||
|
|
||
| - Issue #29822: inspect.isabstract() now works during __init_subclass__. Patch |
There was a problem hiding this comment.
Move this at the start of the Library section. Use two spaces between sentences.
Lib/test/test_inspect.py
Outdated
|
|
||
| def test_isabstract_during_init_subclass(self): | ||
| from abc import ABCMeta, abstractmethod | ||
|
|
There was a problem hiding this comment.
Use blank lines in test method only to indicate logical sections.
- Added a new test. - Cleaned up some code style here and there
Lib/test/test_inspect.py
Outdated
| isabstract_checks.clear() | ||
| class AbstractChild(AbstractClassExample): | ||
| pass | ||
| class AbstractGrandchild(AbstractClassExample): |
There was a problem hiding this comment.
What is the difference between AbstractChild and AbstractGrandchild?
|
Oh, my bad, I meant to have AbstractGrandchild inherit AbstractChild. AbstractGrandchild is not strictly necessary, I suppose; I have it in there to make sure that we're walking the whole |
Lib/inspect.py
Outdated
| # TPFLAGS_IS_ABSTRACT should have been accurate. | ||
| return False | ||
| # It looks like ABCMeta.__new__ has not finished running yet; we're | ||
| # probably in __init_subcalss_. We'll look for abstractmethods manually. |
There was a problem hiding this comment.
Pedantic but typo in comment. Should be:
# probably in __init_subclass__. We'll look for abstractmethods manually.|
Thanks for the review. Fixed the typo, and updated the NEWS file to resolve conflicts. |
|
Bump. @agoose77, I have fixed the aforementioned typo, are there any outstanding issues as far as you're concerned? |
Misc/NEWS
Outdated
|
|
||
| What's New in Python 3.6.0 beta 1? | ||
| ================================== | ||
| What's New in Python 3.6.0 beta ============================== |
There was a problem hiding this comment.
Eaten "1?" and a newline.
|
My bad; thanks for catching that; should be fixed now. |
Lib/inspect.py
Outdated
| # TPFLAGS_IS_ABSTRACT should have been accurate. | ||
| return False | ||
| # It looks like ABCMeta.__new__ has not finished running yet; we're | ||
| # probably in __init_subcalss__. We'll look for abstractmethods manually. |
There was a problem hiding this comment.
Typo: "__init_subcalss__".
|
Fixed thanks. |
ncoghlan
left a comment
There was a problem hiding this comment.
Nice find, and the proposed fix and test case look good to me.
|
Thanks @Soares LGTM👍 |
|
Thank you for your contribution @Soares. Do you mind to backport the fix to 3.6? |
|
I'd be happy to backport. Note that #527 will also need backporting (in Python 3.6, ABCMeta doesn't work with |
…s__. (pythonGH-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.. (cherry picked from commit fcfe80e)
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 onTPFLAGS_IS_ABSTRACT.