-
-
Notifications
You must be signed in to change notification settings - Fork 30k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
bpo-31333: Re-implement ABCMeta in C #5273
Merged
Merged
Changes from 97 commits
Commits
Show all changes
103 commits
Select commit
Hold shift + click to select a range
5c34508
Initial work on C implementation of ABCMeta
ilevkivskyi cb7ffcf
Basic implementation of ABCMeta.__new__
ilevkivskyi b83ee80
Bare-bone implementation of register and subclass checks
ilevkivskyi 181e83f
Fix mock failure and silence compiler warnings
ilevkivskyi c084a7f
Provide nicer dump of registry
ilevkivskyi a192d5d
Add better docstrings
ilevkivskyi 35a2472
Expose the internal cavhes and registry (backward compatibility)
ilevkivskyi 4812450
Merge remote-tracking branch 'upstream/master' into c-abc
ilevkivskyi b9038e2
Merge remote-tracking branch 'upstream/master' into c-abc
bbee578
Add _abc to Setup.dist
a3464fd
Fix _abc in Setup.dist
947bf7d
Update a comment
7ffc59e
Settle the .py version
41287a7
Fix some TODOs and refleaks
569cc44
Fix some more refleaks; use weak refs in registry
34665a8
Some more fixes; add some caching
576acac
Fix a crash due to erroneous DECREF
30098b4
Simplify some code; reorganize TODOs
51ede5d
Finish caches; add more comments
5263e1a
Use Py_RETURN_TRUE/FALSE and fix refleak
methane 1f7aee9
Use Py_RETURN_NONE
methane 11fea70
Use _PyObject_IsAbstract()
methane 7ff3fbb
Use _PySet_NextEntry
methane 9b4eb2f
Minor review comments
ab20a33
Sketch the new API
39f2692
Merge remote-tracking branch 'upstream/master' into c-abc
493d0ec
Use _PyObject_LookupAttr
2fe2c54
Some more progress
9476af6
Implement weakref callbacks and guarded iteration
ab68cdb
Fix some errors
3eb0a60
Fix two review comments
ed36b76
More fixes, test_abc passes
25fc5b9
Merge remote-tracking branch 'upstream/master' into c-abc
b2f75b9
Fix some remaining problems
cdb5cdf
Update TODO
a1a3a52
Add missing statics
a66b08c
Build on Windows
methane 86af9ae
Refactor __abstractmethods__ calculation.
methane b22232a
Refactor via _abc_impl
e51c5ca
Fix some refleaks
bac7a43
Add docs (required by some tests) and initialization
4571649
Merge remote-tracking branch 'upstream/master' into c-abc
0d7513b
Outdated comment and detection of intrusions
c429f49
Minor fixes
357b56d
Few more refleaks
cd80fcb
Restore unwanted changes
34e13c3
Fix(?) some more refleaks
c5633b6
Reset caches between runs
3cbbc12
Fix abuse of borrowed reference
methane 23bcb07
Fix remaining refleaks
0aab479
Updare TODO, switch to common result agreement, few more checks
86e0660
Fix typos
c55e482
Remove irrelevant TODOs, add few comments
5f9526a
Use Py_ssize_t for iterating
methane 8174b61
Use c99 designated initializer.
methane bb8d623
Fix gset_new() and abc_data_new()
methane ef59e54
Massive refactoring...
methane 22699fe
fixup
methane 95cbf34
Review comments
4d596cc
More refactoring
fa3cba3
Typos and minor fixes
6f18293
Minor fixes and code style
9100891
Remove some unreachable code
36c5643
Review comments
dd2abda
Split the class into two separate implementations
99d950c
Add version independent (Py vs C) cache clearing
3762d49
Test the six-like tricky type.__new__(metaclass) with ABCMeta
404d1ce
Test both versions
0dc5fae
Add comment about testing
287b26a
Always DECREF after PyTuplr_Pack
ef34364
Use PySet_New() instead of calling copy() method.
methane d4d78a1
Copy set before iterating, and remove guarded set
methane f58822e
Add NULL check after PyWeakref_GetObject
methane 3b74bdc
Add fast path for looking register
methane db1c852
Check negative cache version before cache lookup
methane 6e62be7
Merge pull request #5 from methane/c-abc-no-guard-set
ilevkivskyi 5384726
Fix nits pointed by pppery.
methane a48eecc
Create set lazily
methane 16a8db1
Explicitly set NULL
methane 5ad3ea8
Merge pull request #6 from methane/c-abc-lazyset
ilevkivskyi 86a9b8d
Check PyObject_IsTrue() error
methane 36c2013
Strip TODO comments.
methane 09c5370
Add NEWS entry
methane 207d8e9
Fix _reset_caches
methane a15377b
Add default: Py_UNREACHABLE()
methane d31da13
Rephrase NEWS entry
methane eaff1cb
Merge remote-tracking branch 'upstream/master' into c-abc
48de70e
Factor out Python version to a separate file
3bd0666
Factor out Python version to a separate file
702347a
Remove extra whitespace
e0c978b
Add more details to NEWS
b370dfe
Fix an import in refleak test
a1ae0a7
Restart tests
4746211
Make order of subclass checks in Python version stable and consistent…
001b416
Convert _abc to Argument Clinic
fc528df
Merge remote-tracking branch 'upstream/master' into c-abc
289c414
Regenerate clinic
ac0c639
Switch from Python invalidation counter to C long long
079e3be
The rest of the comments
9c49e5a
Merge remote-tracking branch 'upstream/master' into c-abc
4146588
Regenerate clinics.
c133605
Two more comments
f82e04d
Few more comments by Serhiy; add Whats New item
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
from _weakrefset import WeakSet | ||
|
||
|
||
def get_cache_token(): | ||
"""Returns the current ABC cache token. | ||
|
||
The token is an opaque object (supporting equality testing) identifying the | ||
current version of the ABC cache for virtual subclasses. The token changes | ||
with every call to ``register()`` on any ABC. | ||
""" | ||
return ABCMeta._abc_invalidation_counter | ||
|
||
|
||
class ABCMeta(type): | ||
"""Metaclass for defining Abstract Base Classes (ABCs). | ||
|
||
Use this metaclass to create an ABC. An ABC can be subclassed | ||
directly, and then acts as a mix-in class. You can also register | ||
unrelated concrete classes (even built-in classes) and unrelated | ||
ABCs as 'virtual subclasses' -- these and their descendants will | ||
be considered subclasses of the registering ABC by the built-in | ||
issubclass() function, but the registering ABC won't show up in | ||
their MRO (Method Resolution Order) nor will method | ||
implementations defined by the registering ABC be callable (not | ||
even via super()). | ||
""" | ||
|
||
# A global counter that is incremented each time a class is | ||
# registered as a virtual subclass of anything. It forces the | ||
# negative cache to be cleared before its next use. | ||
# Note: this counter is private. Use `abc.get_cache_token()` for | ||
# external code. | ||
_abc_invalidation_counter = 0 | ||
|
||
def __new__(mcls, name, bases, namespace, **kwargs): | ||
cls = super().__new__(mcls, name, bases, namespace, **kwargs) | ||
# Compute set of abstract method names | ||
abstracts = {name | ||
for name, value in namespace.items() | ||
if getattr(value, "__isabstractmethod__", False)} | ||
for base in bases: | ||
for name in getattr(base, "__abstractmethods__", set()): | ||
value = getattr(cls, name, None) | ||
if getattr(value, "__isabstractmethod__", False): | ||
abstracts.add(name) | ||
cls.__abstractmethods__ = frozenset(abstracts) | ||
# Set up inheritance registry | ||
cls._abc_registry = WeakSet() | ||
cls._abc_cache = WeakSet() | ||
cls._abc_negative_cache = WeakSet() | ||
cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter | ||
return cls | ||
|
||
def register(cls, subclass): | ||
"""Register a virtual subclass of an ABC. | ||
|
||
Returns the subclass, to allow usage as a class decorator. | ||
""" | ||
if not isinstance(subclass, type): | ||
raise TypeError("Can only register classes") | ||
if issubclass(subclass, cls): | ||
return subclass # Already a subclass | ||
# Subtle: test for cycles *after* testing for "already a subclass"; | ||
# this means we allow X.register(X) and interpret it as a no-op. | ||
if issubclass(cls, subclass): | ||
# This would create a cycle, which is bad for the algorithm below | ||
raise RuntimeError("Refusing to create an inheritance cycle") | ||
cls._abc_registry.add(subclass) | ||
ABCMeta._abc_invalidation_counter += 1 # Invalidate negative cache | ||
return subclass | ||
|
||
def _dump_registry(cls, file=None): | ||
"""Debug helper to print the ABC registry.""" | ||
print(f"Class: {cls.__module__}.{cls.__qualname__}", file=file) | ||
print(f"Inv. counter: {get_cache_token()}", file=file) | ||
for name in cls.__dict__: | ||
if name.startswith("_abc_"): | ||
value = getattr(cls, name) | ||
if isinstance(value, WeakSet): | ||
value = set(value) | ||
print(f"{name}: {value!r}", file=file) | ||
|
||
def _abc_registry_clear(cls): | ||
"""Clear the registry (for debugging or testing).""" | ||
cls._abc_registry.clear() | ||
|
||
def _abc_caches_clear(cls): | ||
"""Clear the caches (for debugging or testing).""" | ||
cls._abc_cache.clear() | ||
cls._abc_negative_cache.clear() | ||
|
||
def __instancecheck__(cls, instance): | ||
"""Override for isinstance(instance, cls).""" | ||
# Inline the cache checking | ||
subclass = instance.__class__ | ||
if subclass in cls._abc_cache: | ||
return True | ||
subtype = type(instance) | ||
if subtype is subclass: | ||
if (cls._abc_negative_cache_version == | ||
ABCMeta._abc_invalidation_counter and | ||
subclass in cls._abc_negative_cache): | ||
return False | ||
# Fall back to the subclass check. | ||
return cls.__subclasscheck__(subclass) | ||
return any(cls.__subclasscheck__(c) for c in (subclass, subtype)) | ||
|
||
def __subclasscheck__(cls, subclass): | ||
"""Override for issubclass(subclass, cls).""" | ||
# Check cache | ||
if subclass in cls._abc_cache: | ||
return True | ||
# Check negative cache; may have to invalidate | ||
if cls._abc_negative_cache_version < ABCMeta._abc_invalidation_counter: | ||
# Invalidate the negative cache | ||
cls._abc_negative_cache = WeakSet() | ||
cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter | ||
elif subclass in cls._abc_negative_cache: | ||
return False | ||
# Check the subclass hook | ||
ok = cls.__subclasshook__(subclass) | ||
if ok is not NotImplemented: | ||
assert isinstance(ok, bool) | ||
if ok: | ||
cls._abc_cache.add(subclass) | ||
else: | ||
cls._abc_negative_cache.add(subclass) | ||
return ok | ||
# Check if it's a direct subclass | ||
if cls in getattr(subclass, '__mro__', ()): | ||
cls._abc_cache.add(subclass) | ||
return True | ||
# Check if it's a subclass of a registered class (recursive) | ||
for rcls in cls._abc_registry: | ||
if issubclass(subclass, rcls): | ||
cls._abc_cache.add(subclass) | ||
return True | ||
# Check if it's a subclass of a subclass (recursive) | ||
for scls in cls.__subclasses__(): | ||
if issubclass(subclass, scls): | ||
cls._abc_cache.add(subclass) | ||
return True | ||
# No dice; update negative cache | ||
cls._abc_negative_cache.add(subclass) | ||
return False |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we fix
ABCMeta.__module__
toabc
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I am not sure. I think it is important for pickling the metaclass itself. But
pickle
can already find the correct class at_py_abc.ABCMeta
. Also it is informative for a quick check which version is used, the C one or the Python one. So this is up to you.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If pickle
abc.ABCMeta
as_py_abc.ABCMeta
it will be not unpickleable in 3.6. Or in future Python versions if we will decide to remove or rename_py_abc
.