Skip to content

Commit 58c86ad

Browse files
authored
Optimize ABC caches (#383)
* Optimize ABC negative cache for generics * Add also positive cache and registry links * Formatting * Add ABC optimization to PY2 * Inline internal helper for more speed * Fix negative cahce setters * Fix negative cahce setters on PY2 * Make negative caches writable only for non-subscripted generics
1 parent d6390f3 commit 58c86ad

File tree

2 files changed

+96
-34
lines changed

2 files changed

+96
-34
lines changed

python2/typing.py

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -929,19 +929,6 @@ def _next_in_mro(cls):
929929
return next_in_mro
930930

931931

932-
def _valid_for_check(cls):
933-
"""An internal helper to prohibit isinstance([1], List[str]) etc."""
934-
if cls is Generic:
935-
raise TypeError("Class %r cannot be used with class "
936-
"or instance checks" % cls)
937-
if (
938-
cls.__origin__ is not None and
939-
sys._getframe(3).f_globals['__name__'] not in ['abc', 'functools']
940-
):
941-
raise TypeError("Parameterized generics cannot be used with class "
942-
"or instance checks")
943-
944-
945932
def _make_subclasshook(cls):
946933
"""Construct a __subclasshook__ callable that incorporates
947934
the associated __extra__ class in subclass checks performed
@@ -952,7 +939,6 @@ def _make_subclasshook(cls):
952939
# Registered classes need not be checked here because
953940
# cls and its extra share the same _abc_registry.
954941
def __extrahook__(cls, subclass):
955-
_valid_for_check(cls)
956942
res = cls.__extra__.__subclasshook__(subclass)
957943
if res is not NotImplemented:
958944
return res
@@ -967,7 +953,6 @@ def __extrahook__(cls, subclass):
967953
else:
968954
# For non-ABC extras we'll just call issubclass().
969955
def __extrahook__(cls, subclass):
970-
_valid_for_check(cls)
971956
if cls.__extra__ and issubclass(subclass, cls.__extra__):
972957
return True
973958
return NotImplemented
@@ -1045,6 +1030,7 @@ def __new__(cls, name, bases, namespace,
10451030
# remove bare Generic from bases if there are other generic bases
10461031
if any(isinstance(b, GenericMeta) and b is not Generic for b in bases):
10471032
bases = tuple(b for b in bases if b is not Generic)
1033+
namespace.update({'__origin__': origin, '__extra__': extra})
10481034
self = super(GenericMeta, cls).__new__(cls, name, bases, namespace)
10491035

10501036
self.__parameters__ = tvars
@@ -1053,8 +1039,6 @@ def __new__(cls, name, bases, namespace,
10531039
self.__args__ = tuple(Ellipsis if a is _TypingEllipsis else
10541040
() if a is _TypingEmpty else
10551041
a for a in args) if args else None
1056-
self.__origin__ = origin
1057-
self.__extra__ = extra
10581042
# Speed hack (https://github.com/python/typing/issues/196).
10591043
self.__next_in_mro__ = _next_in_mro(self)
10601044
# Preserve base classes on subclassing (__bases__ are type erased now).
@@ -1082,6 +1066,42 @@ def __init__(self, *args, **kwargs):
10821066
super(GenericMeta, self).__init__(*args, **kwargs)
10831067
if isinstance(self.__extra__, abc.ABCMeta):
10841068
self._abc_registry = self.__extra__._abc_registry
1069+
self._abc_cache = self.__extra__._abc_cache
1070+
elif self.__origin__ is not None:
1071+
self._abc_registry = self.__origin__._abc_registry
1072+
self._abc_cache = self.__origin__._abc_cache
1073+
1074+
# _abc_negative_cache and _abc_negative_cache_version
1075+
# realised as descriptors, since GenClass[t1, t2, ...] always
1076+
# share subclass info with GenClass.
1077+
# This is an important memory optimization.
1078+
@property
1079+
def _abc_negative_cache(self):
1080+
if isinstance(self.__extra__, abc.ABCMeta):
1081+
return self.__extra__._abc_negative_cache
1082+
return _gorg(self)._abc_generic_negative_cache
1083+
1084+
@_abc_negative_cache.setter
1085+
def _abc_negative_cache(self, value):
1086+
if self.__origin__ is None:
1087+
if isinstance(self.__extra__, abc.ABCMeta):
1088+
self.__extra__._abc_negative_cache = value
1089+
else:
1090+
self._abc_generic_negative_cache = value
1091+
1092+
@property
1093+
def _abc_negative_cache_version(self):
1094+
if isinstance(self.__extra__, abc.ABCMeta):
1095+
return self.__extra__._abc_negative_cache_version
1096+
return _gorg(self)._abc_generic_negative_cache_version
1097+
1098+
@_abc_negative_cache_version.setter
1099+
def _abc_negative_cache_version(self, value):
1100+
if self.__origin__ is None:
1101+
if isinstance(self.__extra__, abc.ABCMeta):
1102+
self.__extra__._abc_negative_cache_version = value
1103+
else:
1104+
self._abc_generic_negative_cache_version = value
10851105

10861106
def _get_type_vars(self, tvars):
10871107
if self.__origin__ and self.__parameters__:
@@ -1181,6 +1201,17 @@ def __getitem__(self, params):
11811201
extra=self.__extra__,
11821202
orig_bases=self.__orig_bases__)
11831203

1204+
def __subclasscheck__(self, cls):
1205+
if self.__origin__ is not None:
1206+
if sys._getframe(1).f_globals['__name__'] not in ['abc', 'functools']:
1207+
raise TypeError("Parameterized generics cannot be used with class "
1208+
"or instance checks")
1209+
return False
1210+
if self is Generic:
1211+
raise TypeError("Class %r cannot be used with class "
1212+
"or instance checks" % self)
1213+
return super(GenericMeta, self).__subclasscheck__(cls)
1214+
11841215
def __instancecheck__(self, instance):
11851216
# Since we extend ABC.__subclasscheck__ and
11861217
# ABC.__instancecheck__ inlines the cache checking done by the

src/typing.py

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -856,19 +856,6 @@ def _next_in_mro(cls):
856856
return next_in_mro
857857

858858

859-
def _valid_for_check(cls):
860-
"""An internal helper to prohibit isinstance([1], List[str]) etc."""
861-
if cls is Generic:
862-
raise TypeError("Class %r cannot be used with class "
863-
"or instance checks" % cls)
864-
if (
865-
cls.__origin__ is not None and
866-
sys._getframe(3).f_globals['__name__'] not in ['abc', 'functools']
867-
):
868-
raise TypeError("Parameterized generics cannot be used with class "
869-
"or instance checks")
870-
871-
872859
def _make_subclasshook(cls):
873860
"""Construct a __subclasshook__ callable that incorporates
874861
the associated __extra__ class in subclass checks performed
@@ -879,7 +866,6 @@ def _make_subclasshook(cls):
879866
# Registered classes need not be checked here because
880867
# cls and its extra share the same _abc_registry.
881868
def __extrahook__(subclass):
882-
_valid_for_check(cls)
883869
res = cls.__extra__.__subclasshook__(subclass)
884870
if res is not NotImplemented:
885871
return res
@@ -894,7 +880,6 @@ def __extrahook__(subclass):
894880
else:
895881
# For non-ABC extras we'll just call issubclass().
896882
def __extrahook__(subclass):
897-
_valid_for_check(cls)
898883
if cls.__extra__ and issubclass(subclass, cls.__extra__):
899884
return True
900885
return NotImplemented
@@ -981,6 +966,7 @@ def __new__(cls, name, bases, namespace,
981966
# remove bare Generic from bases if there are other generic bases
982967
if any(isinstance(b, GenericMeta) and b is not Generic for b in bases):
983968
bases = tuple(b for b in bases if b is not Generic)
969+
namespace.update({'__origin__': origin, '__extra__': extra})
984970
self = super().__new__(cls, name, bases, namespace, _root=True)
985971

986972
self.__parameters__ = tvars
@@ -989,8 +975,6 @@ def __new__(cls, name, bases, namespace,
989975
self.__args__ = tuple(... if a is _TypingEllipsis else
990976
() if a is _TypingEmpty else
991977
a for a in args) if args else None
992-
self.__origin__ = origin
993-
self.__extra__ = extra
994978
# Speed hack (https://github.com/python/typing/issues/196).
995979
self.__next_in_mro__ = _next_in_mro(self)
996980
# Preserve base classes on subclassing (__bases__ are type erased now).
@@ -1009,13 +993,49 @@ def __new__(cls, name, bases, namespace,
1009993
self.__subclasshook__ = _make_subclasshook(self)
1010994
if isinstance(extra, abc.ABCMeta):
1011995
self._abc_registry = extra._abc_registry
996+
self._abc_cache = extra._abc_cache
997+
elif origin is not None:
998+
self._abc_registry = origin._abc_registry
999+
self._abc_cache = origin._abc_cache
10121000

10131001
if origin and hasattr(origin, '__qualname__'): # Fix for Python 3.2.
10141002
self.__qualname__ = origin.__qualname__
10151003
self.__tree_hash__ = (hash(self._subs_tree()) if origin else
10161004
super(GenericMeta, self).__hash__())
10171005
return self
10181006

1007+
# _abc_negative_cache and _abc_negative_cache_version
1008+
# realised as descriptors, since GenClass[t1, t2, ...] always
1009+
# share subclass info with GenClass.
1010+
# This is an important memory optimization.
1011+
@property
1012+
def _abc_negative_cache(self):
1013+
if isinstance(self.__extra__, abc.ABCMeta):
1014+
return self.__extra__._abc_negative_cache
1015+
return _gorg(self)._abc_generic_negative_cache
1016+
1017+
@_abc_negative_cache.setter
1018+
def _abc_negative_cache(self, value):
1019+
if self.__origin__ is None:
1020+
if isinstance(self.__extra__, abc.ABCMeta):
1021+
self.__extra__._abc_negative_cache = value
1022+
else:
1023+
self._abc_generic_negative_cache = value
1024+
1025+
@property
1026+
def _abc_negative_cache_version(self):
1027+
if isinstance(self.__extra__, abc.ABCMeta):
1028+
return self.__extra__._abc_negative_cache_version
1029+
return _gorg(self)._abc_generic_negative_cache_version
1030+
1031+
@_abc_negative_cache_version.setter
1032+
def _abc_negative_cache_version(self, value):
1033+
if self.__origin__ is None:
1034+
if isinstance(self.__extra__, abc.ABCMeta):
1035+
self.__extra__._abc_negative_cache_version = value
1036+
else:
1037+
self._abc_generic_negative_cache_version = value
1038+
10191039
def _get_type_vars(self, tvars):
10201040
if self.__origin__ and self.__parameters__:
10211041
_get_type_vars(self.__parameters__, tvars)
@@ -1114,6 +1134,17 @@ def __getitem__(self, params):
11141134
extra=self.__extra__,
11151135
orig_bases=self.__orig_bases__)
11161136

1137+
def __subclasscheck__(self, cls):
1138+
if self.__origin__ is not None:
1139+
if sys._getframe(1).f_globals['__name__'] not in ['abc', 'functools']:
1140+
raise TypeError("Parameterized generics cannot be used with class "
1141+
"or instance checks")
1142+
return False
1143+
if self is Generic:
1144+
raise TypeError("Class %r cannot be used with class "
1145+
"or instance checks" % self)
1146+
return super().__subclasscheck__(cls)
1147+
11171148
def __instancecheck__(self, instance):
11181149
# Since we extend ABC.__subclasscheck__ and
11191150
# ABC.__instancecheck__ inlines the cache checking done by the

0 commit comments

Comments
 (0)