bpo-31333: Re-implement ABCMeta in C (GH-5733) · python/cpython@3892899 (original) (raw)
``
1
`+
from _weakrefset import WeakSet
`
``
2
+
``
3
+
``
4
`+
def get_cache_token():
`
``
5
`+
"""Returns the current ABC cache token.
`
``
6
+
``
7
`+
The token is an opaque object (supporting equality testing) identifying the
`
``
8
`+
current version of the ABC cache for virtual subclasses. The token changes
`
``
9
with every call to ``register()`` on any ABC.
``
10
`+
"""
`
``
11
`+
return ABCMeta._abc_invalidation_counter
`
``
12
+
``
13
+
``
14
`+
class ABCMeta(type):
`
``
15
`+
"""Metaclass for defining Abstract Base Classes (ABCs).
`
``
16
+
``
17
`+
Use this metaclass to create an ABC. An ABC can be subclassed
`
``
18
`+
directly, and then acts as a mix-in class. You can also register
`
``
19
`+
unrelated concrete classes (even built-in classes) and unrelated
`
``
20
`+
ABCs as 'virtual subclasses' -- these and their descendants will
`
``
21
`+
be considered subclasses of the registering ABC by the built-in
`
``
22
`+
issubclass() function, but the registering ABC won't show up in
`
``
23
`+
their MRO (Method Resolution Order) nor will method
`
``
24
`+
implementations defined by the registering ABC be callable (not
`
``
25
`+
even via super()).
`
``
26
`+
"""
`
``
27
+
``
28
`+
A global counter that is incremented each time a class is
`
``
29
`+
registered as a virtual subclass of anything. It forces the
`
``
30
`+
negative cache to be cleared before its next use.
`
``
31
`` +
Note: this counter is private. Use abc.get_cache_token()
for
``
``
32
`+
external code.
`
``
33
`+
_abc_invalidation_counter = 0
`
``
34
+
``
35
`+
def new(mcls, name, bases, namespace, **kwargs):
`
``
36
`+
cls = super().new(mcls, name, bases, namespace, **kwargs)
`
``
37
`+
Compute set of abstract method names
`
``
38
`+
abstracts = {name
`
``
39
`+
for name, value in namespace.items()
`
``
40
`+
if getattr(value, "isabstractmethod", False)}
`
``
41
`+
for base in bases:
`
``
42
`+
for name in getattr(base, "abstractmethods", set()):
`
``
43
`+
value = getattr(cls, name, None)
`
``
44
`+
if getattr(value, "isabstractmethod", False):
`
``
45
`+
abstracts.add(name)
`
``
46
`+
cls.abstractmethods = frozenset(abstracts)
`
``
47
`+
Set up inheritance registry
`
``
48
`+
cls._abc_registry = WeakSet()
`
``
49
`+
cls._abc_cache = WeakSet()
`
``
50
`+
cls._abc_negative_cache = WeakSet()
`
``
51
`+
cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter
`
``
52
`+
return cls
`
``
53
+
``
54
`+
def register(cls, subclass):
`
``
55
`+
"""Register a virtual subclass of an ABC.
`
``
56
+
``
57
`+
Returns the subclass, to allow usage as a class decorator.
`
``
58
`+
"""
`
``
59
`+
if not isinstance(subclass, type):
`
``
60
`+
raise TypeError("Can only register classes")
`
``
61
`+
if issubclass(subclass, cls):
`
``
62
`+
return subclass # Already a subclass
`
``
63
`+
Subtle: test for cycles after testing for "already a subclass";
`
``
64
`+
this means we allow X.register(X) and interpret it as a no-op.
`
``
65
`+
if issubclass(cls, subclass):
`
``
66
`+
This would create a cycle, which is bad for the algorithm below
`
``
67
`+
raise RuntimeError("Refusing to create an inheritance cycle")
`
``
68
`+
cls._abc_registry.add(subclass)
`
``
69
`+
ABCMeta._abc_invalidation_counter += 1 # Invalidate negative cache
`
``
70
`+
return subclass
`
``
71
+
``
72
`+
def _dump_registry(cls, file=None):
`
``
73
`+
"""Debug helper to print the ABC registry."""
`
``
74
`+
print(f"Class: {cls.module}.{cls.qualname}", file=file)
`
``
75
`+
print(f"Inv. counter: {get_cache_token()}", file=file)
`
``
76
`+
for name in cls.dict:
`
``
77
`+
if name.startswith("abc"):
`
``
78
`+
value = getattr(cls, name)
`
``
79
`+
if isinstance(value, WeakSet):
`
``
80
`+
value = set(value)
`
``
81
`+
print(f"{name}: {value!r}", file=file)
`
``
82
+
``
83
`+
def _abc_registry_clear(cls):
`
``
84
`+
"""Clear the registry (for debugging or testing)."""
`
``
85
`+
cls._abc_registry.clear()
`
``
86
+
``
87
`+
def _abc_caches_clear(cls):
`
``
88
`+
"""Clear the caches (for debugging or testing)."""
`
``
89
`+
cls._abc_cache.clear()
`
``
90
`+
cls._abc_negative_cache.clear()
`
``
91
+
``
92
`+
def instancecheck(cls, instance):
`
``
93
`+
"""Override for isinstance(instance, cls)."""
`
``
94
`+
Inline the cache checking
`
``
95
`+
subclass = instance.class
`
``
96
`+
if subclass in cls._abc_cache:
`
``
97
`+
return True
`
``
98
`+
subtype = type(instance)
`
``
99
`+
if subtype is subclass:
`
``
100
`+
if (cls._abc_negative_cache_version ==
`
``
101
`+
ABCMeta._abc_invalidation_counter and
`
``
102
`+
subclass in cls._abc_negative_cache):
`
``
103
`+
return False
`
``
104
`+
Fall back to the subclass check.
`
``
105
`+
return cls.subclasscheck(subclass)
`
``
106
`+
return any(cls.subclasscheck(c) for c in (subclass, subtype))
`
``
107
+
``
108
`+
def subclasscheck(cls, subclass):
`
``
109
`+
"""Override for issubclass(subclass, cls)."""
`
``
110
`+
Check cache
`
``
111
`+
if subclass in cls._abc_cache:
`
``
112
`+
return True
`
``
113
`+
Check negative cache; may have to invalidate
`
``
114
`+
if cls._abc_negative_cache_version < ABCMeta._abc_invalidation_counter:
`
``
115
`+
Invalidate the negative cache
`
``
116
`+
cls._abc_negative_cache = WeakSet()
`
``
117
`+
cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter
`
``
118
`+
elif subclass in cls._abc_negative_cache:
`
``
119
`+
return False
`
``
120
`+
Check the subclass hook
`
``
121
`+
ok = cls.subclasshook(subclass)
`
``
122
`+
if ok is not NotImplemented:
`
``
123
`+
assert isinstance(ok, bool)
`
``
124
`+
if ok:
`
``
125
`+
cls._abc_cache.add(subclass)
`
``
126
`+
else:
`
``
127
`+
cls._abc_negative_cache.add(subclass)
`
``
128
`+
return ok
`
``
129
`+
Check if it's a direct subclass
`
``
130
`+
if cls in getattr(subclass, 'mro', ()):
`
``
131
`+
cls._abc_cache.add(subclass)
`
``
132
`+
return True
`
``
133
`+
Check if it's a subclass of a registered class (recursive)
`
``
134
`+
for rcls in cls._abc_registry:
`
``
135
`+
if issubclass(subclass, rcls):
`
``
136
`+
cls._abc_cache.add(subclass)
`
``
137
`+
return True
`
``
138
`+
Check if it's a subclass of a subclass (recursive)
`
``
139
`+
for scls in cls.subclasses():
`
``
140
`+
if issubclass(subclass, scls):
`
``
141
`+
cls._abc_cache.add(subclass)
`
``
142
`+
return True
`
``
143
`+
No dice; update negative cache
`
``
144
`+
cls._abc_negative_cache.add(subclass)
`
``
145
`+
return False
`