From e41612722ea47c93fed58774374cf2bb2e897db4 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 11 Jun 2017 14:01:05 +0200 Subject: [PATCH 01/14] Inline _geqv and _gorg --- src/typing.py | 73 ++++++++++++++++++--------------------------------- 1 file changed, 26 insertions(+), 47 deletions(-) diff --git a/src/typing.py b/src/typing.py index c487afcb5..a7377cf63 100644 --- a/src/typing.py +++ b/src/typing.py @@ -376,7 +376,7 @@ def _type_check(arg, msg): if ( type(arg).__name__ in ('_Union', '_Optional') and not getattr(arg, '__origin__', None) or - isinstance(arg, TypingMeta) and _gorg(arg) in (Generic, _Protocol) + isinstance(arg, TypingMeta) and arg._gorg in (Generic, _Protocol) ): raise TypeError("Plain %s is not valid as type argument" % arg) return arg @@ -849,29 +849,6 @@ def __getitem__(self, arg): Optional = _Optional(_root=True) -def _gorg(a): - """Return the farthest origin of a generic class (internal helper).""" - assert isinstance(a, GenericMeta) - while a.__origin__ is not None: - a = a.__origin__ - return a - - -def _geqv(a, b): - """Return whether two generic classes are equivalent (internal helper). - - The intention is to consider generic class X and any of its - parameterized forms (X[T], X[int], etc.) as equivalent. - - However, X is not equivalent to a subclass of X. - - The relation is reflexive, symmetric and transitive. - """ - assert isinstance(a, GenericMeta) and isinstance(b, GenericMeta) - # Reduce each to its origin. - return _gorg(a) is _gorg(b) - - def _next_in_mro(cls): """Helper for Generic.__new__. @@ -881,7 +858,7 @@ def _next_in_mro(cls): next_in_mro = object # Look for the last occurrence of Generic or Generic[...]. for i, c in enumerate(cls.__mro__[:-1]): - if isinstance(c, GenericMeta) and _gorg(c) is Generic: + if isinstance(c, GenericMeta) and c._gorg is Generic: next_in_mro = cls.__mro__[i + 1] return next_in_mro @@ -991,13 +968,14 @@ def __new__(cls, name, bases, namespace, initial_bases = bases if extra is not None and type(extra) is abc.ABCMeta and extra not in bases: bases = (extra,) + bases - bases = tuple(_gorg(b) if isinstance(b, GenericMeta) else b for b in bases) + bases = tuple(b._gorg if isinstance(b, GenericMeta) else b for b in bases) # remove bare Generic from bases if there are other generic bases if any(isinstance(b, GenericMeta) and b is not Generic for b in bases): bases = tuple(b for b in bases if b is not Generic) namespace.update({'__origin__': origin, '__extra__': extra}) self = super().__new__(cls, name, bases, namespace, _root=True) + super(GenericMeta, self).__setattr__('_gorg', self if not origin else origin._gorg) self.__parameters__ = tvars # Be prepared that GenericMeta will be subclassed by TupleMeta @@ -1041,7 +1019,7 @@ def __new__(cls, name, bases, namespace, def _abc_negative_cache(self): if isinstance(self.__extra__, abc.ABCMeta): return self.__extra__._abc_negative_cache - return _gorg(self)._abc_generic_negative_cache + return self._gorg._abc_generic_negative_cache @_abc_negative_cache.setter def _abc_negative_cache(self, value): @@ -1055,7 +1033,7 @@ def _abc_negative_cache(self, value): def _abc_negative_cache_version(self): if isinstance(self.__extra__, abc.ABCMeta): return self.__extra__._abc_negative_cache_version - return _gorg(self)._abc_generic_negative_cache_version + return self._gorg._abc_generic_negative_cache_version @_abc_negative_cache_version.setter def _abc_negative_cache_version(self, value): @@ -1105,7 +1083,7 @@ def _subs_tree(self, tvars=None, args=None): if self.__origin__ is None: return self tree_args = _subs_tree(self, tvars, args) - return (_gorg(self),) + tuple(tree_args) + return (self._gorg,) + tuple(tree_args) def __eq__(self, other): if not isinstance(other, GenericMeta): @@ -1121,7 +1099,7 @@ def __hash__(self): def __getitem__(self, params): if not isinstance(params, tuple): params = (params,) - if not params and not _gorg(self) is Tuple: + if not params and self._gorg is not Tuple: raise TypeError( "Parameter list to %s[...] cannot be empty" % _qualname(self)) msg = "Parameters to generic types must be types." @@ -1196,7 +1174,7 @@ def __setattr__(self, attr, value): ): super(GenericMeta, self).__setattr__(attr, value) else: - super(GenericMeta, _gorg(self)).__setattr__(attr, value) + super(GenericMeta, self._gorg).__setattr__(attr, value) # Prevent checks for Generic to crash when defining Generic. @@ -1209,7 +1187,7 @@ def _generic_new(base_cls, cls, *args, **kwds): if cls.__origin__ is None: return base_cls.__new__(cls) else: - origin = _gorg(cls) + origin = cls._gorg obj = base_cls.__new__(origin) try: obj.__orig_class__ = cls @@ -1243,7 +1221,7 @@ def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT: __slots__ = () def __new__(cls, *args, **kwds): - if _geqv(cls, Generic): + if cls._gorg is Generic: raise TypeError("Type Generic cannot be instantiated; " "it can be used only as a base class") return _generic_new(cls.__next_in_mro__, cls, *args, **kwds) @@ -1265,7 +1243,7 @@ class TupleMeta(GenericMeta): @_tp_cache def __getitem__(self, parameters): - if self.__origin__ is not None or not _geqv(self, Tuple): + if self.__origin__ is not None or self._gorg is not Tuple: # Normal generic rules apply if this is not the first subscription # or a subscription of a subclass. return super().__getitem__(parameters) @@ -1307,7 +1285,7 @@ class Tuple(tuple, extra=tuple, metaclass=TupleMeta): __slots__ = () def __new__(cls, *args, **kwds): - if _geqv(cls, Tuple): + if cls._gorg is Tuple: raise TypeError("Type Tuple cannot be instantiated; " "use tuple() instead") return _generic_new(tuple, cls, *args, **kwds) @@ -1322,7 +1300,7 @@ def __repr__(self): return self._tree_repr(self._subs_tree()) def _tree_repr(self, tree): - if _gorg(self) is not Callable: + if self._gorg is not Callable: return super()._tree_repr(tree) # For actual Callable (not its subclass) we override # super()._tree_repr() for nice formatting. @@ -1342,7 +1320,7 @@ def __getitem__(self, parameters): with hashable arguments to improve speed. """ - if self.__origin__ is not None or not _geqv(self, Callable): + if self.__origin__ is not None or self._gorg is not Callable: return super().__getitem__(parameters) if not isinstance(parameters, tuple) or len(parameters) != 2: raise TypeError("Callable must be used as " @@ -1384,7 +1362,7 @@ class Callable(extra=collections_abc.Callable, metaclass=CallableMeta): __slots__ = () def __new__(cls, *args, **kwds): - if _geqv(cls, Callable): + if cls._gorg is Callable: raise TypeError("Type Callable cannot be instantiated; " "use a non-abstract subclass instead") return _generic_new(cls.__next_in_mro__, cls, *args, **kwds) @@ -1687,6 +1665,7 @@ def _get_protocol_attrs(self): attr != '__annotations__' and attr != '__weakref__' and attr != '_is_protocol' and + attr != '_gorg' and attr != '__dict__' and attr != '__args__' and attr != '__slots__' and @@ -1892,7 +1871,7 @@ class List(list, MutableSequence[T], extra=list): __slots__ = () def __new__(cls, *args, **kwds): - if _geqv(cls, List): + if cls._gorg is List: raise TypeError("Type List cannot be instantiated; " "use list() instead") return _generic_new(list, cls, *args, **kwds) @@ -1903,7 +1882,7 @@ class Deque(collections.deque, MutableSequence[T], extra=collections.deque): __slots__ = () def __new__(cls, *args, **kwds): - if _geqv(cls, Deque): + if cls._gorg is Deque: return collections.deque(*args, **kwds) return _generic_new(collections.deque, cls, *args, **kwds) @@ -1913,7 +1892,7 @@ class Set(set, MutableSet[T], extra=set): __slots__ = () def __new__(cls, *args, **kwds): - if _geqv(cls, Set): + if cls._gorg is Set: raise TypeError("Type Set cannot be instantiated; " "use set() instead") return _generic_new(set, cls, *args, **kwds) @@ -1923,7 +1902,7 @@ class FrozenSet(frozenset, AbstractSet[T_co], extra=frozenset): __slots__ = () def __new__(cls, *args, **kwds): - if _geqv(cls, FrozenSet): + if cls._gorg is FrozenSet: raise TypeError("Type FrozenSet cannot be instantiated; " "use frozenset() instead") return _generic_new(frozenset, cls, *args, **kwds) @@ -2014,7 +1993,7 @@ class Dict(dict, MutableMapping[KT, VT], extra=dict): __slots__ = () def __new__(cls, *args, **kwds): - if _geqv(cls, Dict): + if cls._gorg is Dict: raise TypeError("Type Dict cannot be instantiated; " "use dict() instead") return _generic_new(dict, cls, *args, **kwds) @@ -2026,7 +2005,7 @@ class DefaultDict(collections.defaultdict, MutableMapping[KT, VT], __slots__ = () def __new__(cls, *args, **kwds): - if _geqv(cls, DefaultDict): + if cls._gorg is DefaultDict: return collections.defaultdict(*args, **kwds) return _generic_new(collections.defaultdict, cls, *args, **kwds) @@ -2036,7 +2015,7 @@ class Counter(collections.Counter, Dict[T, int], extra=collections.Counter): __slots__ = () def __new__(cls, *args, **kwds): - if _geqv(cls, Counter): + if cls._gorg is Counter: return collections.Counter(*args, **kwds) return _generic_new(collections.Counter, cls, *args, **kwds) @@ -2051,7 +2030,7 @@ class ChainMap(collections.ChainMap, MutableMapping[KT, VT], __slots__ = () def __new__(cls, *args, **kwds): - if _geqv(cls, ChainMap): + if cls._gorg is ChainMap: return collections.ChainMap(*args, **kwds) return _generic_new(collections.ChainMap, cls, *args, **kwds) @@ -2070,7 +2049,7 @@ class Generator(Iterator[T_co], Generic[T_co, T_contra, V_co], __slots__ = () def __new__(cls, *args, **kwds): - if _geqv(cls, Generator): + if cls._gorg is Generator: raise TypeError("Type Generator cannot be instantiated; " "create a subclass instead") return _generic_new(_G_base, cls, *args, **kwds) From 9c60a07a30f7a0fc370238dc63c74c02e89e38e8 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 11 Jun 2017 14:33:46 +0200 Subject: [PATCH 02/14] Fix typo --- src/typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/typing.py b/src/typing.py index a7377cf63..c267b1a82 100644 --- a/src/typing.py +++ b/src/typing.py @@ -1167,7 +1167,7 @@ def __copy__(self): self.__extra__, self.__orig_bases__) def __setattr__(self, attr, value): - # We consider all the subscripted genrics as proxies for original class + # We consider all the subscripted generics as proxies for original class if ( attr.startswith('__') and attr.endswith('__') or attr.startswith('_abc_') From 7d192a4b191151fa236e717df9c93ddcc901e58b Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 11 Jun 2017 15:47:46 +0200 Subject: [PATCH 03/14] Cut MROs in half --- src/typing.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/typing.py b/src/typing.py index c267b1a82..1fe33f392 100644 --- a/src/typing.py +++ b/src/typing.py @@ -968,7 +968,18 @@ def __new__(cls, name, bases, namespace, initial_bases = bases if extra is not None and type(extra) is abc.ABCMeta and extra not in bases: bases = (extra,) + bases - bases = tuple(b._gorg if isinstance(b, GenericMeta) else b for b in bases) + new_bases = [] + for base in bases: + if isinstance(base, GenericMeta): + bextra = getattr(base, '__extra__', None) + if (extra and bextra and not origin and bextra not in bases + and type(bextra) is abc.ABCMeta): + new_bases.append(bextra) + else: + new_bases.append(base._gorg) + else: + new_bases.append(base) + bases = tuple(new_bases) # remove bare Generic from bases if there are other generic bases if any(isinstance(b, GenericMeta) and b is not Generic for b in bases): From f5881a248233d6822a940356caa2ac39f2bde173 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 11 Jun 2017 16:10:11 +0200 Subject: [PATCH 04/14] Avoid recursion on _gorg --- src/typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/typing.py b/src/typing.py index 1fe33f392..50278c696 100644 --- a/src/typing.py +++ b/src/typing.py @@ -1557,7 +1557,7 @@ def no_type_check(arg): if isinstance(arg, type): arg_attrs = arg.__dict__.copy() for attr, val in arg.__dict__.items(): - if val in arg.__bases__: + if val in arg.__bases__ + (arg,): arg_attrs.pop(attr) for obj in arg_attrs.values(): if isinstance(obj, types.FunctionType): From 03185712849f49372ab6d741e2cca8557b3abaaa Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 11 Jun 2017 16:16:41 +0200 Subject: [PATCH 05/14] Fix lint --- src/typing.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/typing.py b/src/typing.py index 50278c696..049d36d1a 100644 --- a/src/typing.py +++ b/src/typing.py @@ -972,8 +972,8 @@ def __new__(cls, name, bases, namespace, for base in bases: if isinstance(base, GenericMeta): bextra = getattr(base, '__extra__', None) - if (extra and bextra and not origin and bextra not in bases - and type(bextra) is abc.ABCMeta): + if (extra and bextra and not origin and bextra not in bases and + type(bextra) is abc.ABCMeta): new_bases.append(bextra) else: new_bases.append(base._gorg) @@ -986,8 +986,8 @@ def __new__(cls, name, bases, namespace, bases = tuple(b for b in bases if b is not Generic) namespace.update({'__origin__': origin, '__extra__': extra}) self = super().__new__(cls, name, bases, namespace, _root=True) - super(GenericMeta, self).__setattr__('_gorg', self if not origin else origin._gorg) - + super(GenericMeta, self).__setattr__('_gorg', self if not origin + else origin._gorg) self.__parameters__ = tvars # Be prepared that GenericMeta will be subclassed by TupleMeta # and CallableMeta, those two allow ..., (), or [] in __args___. From e84c51fd05b11f6b8cbb0c788318bd431dcfc302 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 11 Jun 2017 16:20:25 +0200 Subject: [PATCH 06/14] Fxi lint (for realz) --- src/typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/typing.py b/src/typing.py index 049d36d1a..bab4f9d69 100644 --- a/src/typing.py +++ b/src/typing.py @@ -987,7 +987,7 @@ def __new__(cls, name, bases, namespace, namespace.update({'__origin__': origin, '__extra__': extra}) self = super().__new__(cls, name, bases, namespace, _root=True) super(GenericMeta, self).__setattr__('_gorg', self if not origin - else origin._gorg) + else origin._gorg) self.__parameters__ = tvars # Be prepared that GenericMeta will be subclassed by TupleMeta # and CallableMeta, those two allow ..., (), or [] in __args___. From b5152e2363b30f07a9ca303bf2755590015840db Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 12 Jun 2017 13:43:55 +0200 Subject: [PATCH 07/14] Refactor erasure logic to make it clearer (and probably faster) --- src/typing.py | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/typing.py b/src/typing.py index bab4f9d69..09bccfb89 100644 --- a/src/typing.py +++ b/src/typing.py @@ -966,24 +966,26 @@ def __new__(cls, name, bases, namespace, tvars = gvars initial_bases = bases - if extra is not None and type(extra) is abc.ABCMeta and extra not in bases: - bases = (extra,) + bases - new_bases = [] - for base in bases: - if isinstance(base, GenericMeta): - bextra = getattr(base, '__extra__', None) - if (extra and bextra and not origin and bextra not in bases and - type(bextra) is abc.ABCMeta): - new_bases.append(bextra) - else: - new_bases.append(base._gorg) - else: - new_bases.append(base) - bases = tuple(new_bases) - - # remove bare Generic from bases if there are other generic bases - if any(isinstance(b, GenericMeta) and b is not Generic for b in bases): - bases = tuple(b for b in bases if b is not Generic) + if not origin: + # Erase base classes + new_bases = [] + for base in bases: + erased = base + if isinstance(base, GenericMeta): + erased = base._gorg + if extra: # Even stronger erasure for generic ABCs in typing + bextra = getattr(base, '__extra__', None) + erased = bextra if bextra else erased + if erased not in new_bases: + new_bases.append(erased) + + bases = tuple(new_bases) + if type(extra) is abc.ABCMeta and extra not in bases: + bases = (extra,) + bases + + # Remove bare Generic from bases if there are other generic bases + if any(isinstance(b, GenericMeta) and b is not Generic for b in bases): + bases = tuple(b for b in bases if b is not Generic) namespace.update({'__origin__': origin, '__extra__': extra}) self = super().__new__(cls, name, bases, namespace, _root=True) super(GenericMeta, self).__setattr__('_gorg', self if not origin From 369b252f85440a3f1b24c3bab254a2ebc5d310fb Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 12 Jun 2017 14:03:31 +0200 Subject: [PATCH 08/14] Remove redundant check --- src/typing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/typing.py b/src/typing.py index 09bccfb89..479ee7d03 100644 --- a/src/typing.py +++ b/src/typing.py @@ -976,8 +976,8 @@ def __new__(cls, name, bases, namespace, if extra: # Even stronger erasure for generic ABCs in typing bextra = getattr(base, '__extra__', None) erased = bextra if bextra else erased - if erased not in new_bases: - new_bases.append(erased) + #if erased not in new_bases: + new_bases.append(erased) bases = tuple(new_bases) if type(extra) is abc.ABCMeta and extra not in bases: From 0d3dde6524e1c14b513829f98f2cec14dd5b9220 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 12 Jun 2017 14:33:08 +0200 Subject: [PATCH 09/14] Remove unnecessary comment --- src/typing.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/typing.py b/src/typing.py index 479ee7d03..30794758e 100644 --- a/src/typing.py +++ b/src/typing.py @@ -976,7 +976,6 @@ def __new__(cls, name, bases, namespace, if extra: # Even stronger erasure for generic ABCs in typing bextra = getattr(base, '__extra__', None) erased = bextra if bextra else erased - #if erased not in new_bases: new_bases.append(erased) bases = tuple(new_bases) From ba3ffbd624fd68e8980d2bfe6bbd029b42b6ca05 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 12 Jun 2017 14:49:54 +0200 Subject: [PATCH 10/14] Backport first part to Python 2 --- python2/typing.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/python2/typing.py b/python2/typing.py index 0a9d674b9..64c93a4d2 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -1074,15 +1074,25 @@ def __new__(cls, name, bases, namespace, tvars = gvars initial_bases = bases - if extra is None: - extra = namespace.get('__extra__') - if extra is not None and type(extra) is abc.ABCMeta and extra not in bases: - bases = (extra,) + bases - bases = tuple(_gorg(b) if isinstance(b, GenericMeta) else b for b in bases) - - # remove bare Generic from bases if there are other generic bases - if any(isinstance(b, GenericMeta) and b is not Generic for b in bases): - bases = tuple(b for b in bases if b is not Generic) + if not origin: + # Erase base classes + new_bases = [] + for base in bases: + erased = base + if isinstance(base, GenericMeta): + erased = _gorg(base) + if extra: # Even stronger erasure for generic ABCs in typing + bextra = getattr(base, '__extra__', None) + erased = bextra if bextra else erased + new_bases.append(erased) + + bases = tuple(new_bases) + if type(extra) is abc.ABCMeta and extra not in bases: + bases = (extra,) + bases + + # Remove bare Generic from bases if there are other generic bases + if any(isinstance(b, GenericMeta) and b is not Generic for b in bases): + bases = tuple(b for b in bases if b is not Generic) namespace.update({'__origin__': origin, '__extra__': extra}) self = super(GenericMeta, cls).__new__(cls, name, bases, namespace) From 630aeef45832b9f2167791dccde732a60b664816 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 12 Jun 2017 14:58:02 +0200 Subject: [PATCH 11/14] Backport second part to Python 2 --- python2/typing.py | 71 +++++++++++++++++------------------------------ 1 file changed, 25 insertions(+), 46 deletions(-) diff --git a/python2/typing.py b/python2/typing.py index 64c93a4d2..836f028f7 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -357,7 +357,7 @@ def _type_check(arg, msg): if ( type(arg).__name__ in ('_Union', '_Optional') and not getattr(arg, '__origin__', None) or - isinstance(arg, TypingMeta) and _gorg(arg) in (Generic, _Protocol) + isinstance(arg, TypingMeta) and arg._gorg in (Generic, _Protocol) ): raise TypeError("Plain %s is not valid as type argument" % arg) return arg @@ -945,29 +945,6 @@ def __getitem__(self, arg): Optional = _Optional(_root=True) -def _gorg(a): - """Return the farthest origin of a generic class (internal helper).""" - assert isinstance(a, GenericMeta) - while a.__origin__ is not None: - a = a.__origin__ - return a - - -def _geqv(a, b): - """Return whether two generic classes are equivalent (internal helper). - - The intention is to consider generic class X and any of its - parameterized forms (X[T], X[int], etc.) as equivalent. - - However, X is not equivalent to a subclass of X. - - The relation is reflexive, symmetric and transitive. - """ - assert isinstance(a, GenericMeta) and isinstance(b, GenericMeta) - # Reduce each to its origin. - return _gorg(a) is _gorg(b) - - def _next_in_mro(cls): """Helper for Generic.__new__. @@ -977,7 +954,7 @@ def _next_in_mro(cls): next_in_mro = object # Look for the last occurrence of Generic or Generic[...]. for i, c in enumerate(cls.__mro__[:-1]): - if isinstance(c, GenericMeta) and _gorg(c) is Generic: + if isinstance(c, GenericMeta) and c._gorg is Generic: next_in_mro = cls.__mro__[i + 1] return next_in_mro @@ -1080,7 +1057,7 @@ def __new__(cls, name, bases, namespace, for base in bases: erased = base if isinstance(base, GenericMeta): - erased = _gorg(base) + erased = base._gorg if extra: # Even stronger erasure for generic ABCs in typing bextra = getattr(base, '__extra__', None) erased = bextra if bextra else erased @@ -1095,6 +1072,8 @@ def __new__(cls, name, bases, namespace, bases = tuple(b for b in bases if b is not Generic) namespace.update({'__origin__': origin, '__extra__': extra}) self = super(GenericMeta, cls).__new__(cls, name, bases, namespace) + super(GenericMeta, self).__setattr__('_gorg', self if not origin + else origin._gorg) self.__parameters__ = tvars # Be prepared that GenericMeta will be subclassed by TupleMeta @@ -1141,7 +1120,7 @@ def __init__(self, *args, **kwargs): def _abc_negative_cache(self): if isinstance(self.__extra__, abc.ABCMeta): return self.__extra__._abc_negative_cache - return _gorg(self)._abc_generic_negative_cache + return self._gorg._abc_generic_negative_cache @_abc_negative_cache.setter def _abc_negative_cache(self, value): @@ -1155,7 +1134,7 @@ def _abc_negative_cache(self, value): def _abc_negative_cache_version(self): if isinstance(self.__extra__, abc.ABCMeta): return self.__extra__._abc_negative_cache_version - return _gorg(self)._abc_generic_negative_cache_version + return self._gorg._abc_generic_negative_cache_version @_abc_negative_cache_version.setter def _abc_negative_cache_version(self, value): @@ -1205,7 +1184,7 @@ def _subs_tree(self, tvars=None, args=None): if self.__origin__ is None: return self tree_args = _subs_tree(self, tvars, args) - return (_gorg(self),) + tuple(tree_args) + return (self._gorg,) + tuple(tree_args) def __eq__(self, other): if not isinstance(other, GenericMeta): @@ -1221,7 +1200,7 @@ def __hash__(self): def __getitem__(self, params): if not isinstance(params, tuple): params = (params,) - if not params and not _gorg(self) is Tuple: + if not params and self._gorg is not Tuple: raise TypeError( "Parameter list to %s[...] cannot be empty" % _qualname(self)) msg = "Parameters to generic types must be types." @@ -1297,7 +1276,7 @@ def __setattr__(self, attr, value): ): super(GenericMeta, self).__setattr__(attr, value) else: - super(GenericMeta, _gorg(self)).__setattr__(attr, value) + super(GenericMeta, self._gorg).__setattr__(attr, value) # Prevent checks for Generic to crash when defining Generic. @@ -1310,7 +1289,7 @@ def _generic_new(base_cls, cls, *args, **kwds): if cls.__origin__ is None: return base_cls.__new__(cls) else: - origin = _gorg(cls) + origin = cls._gorg obj = base_cls.__new__(origin) try: obj.__orig_class__ = cls @@ -1345,7 +1324,7 @@ def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT: __slots__ = () def __new__(cls, *args, **kwds): - if _geqv(cls, Generic): + if cls._gorg is Generic: raise TypeError("Type Generic cannot be instantiated; " "it can be used only as a base class") return _generic_new(cls.__next_in_mro__, cls, *args, **kwds) @@ -1367,7 +1346,7 @@ class TupleMeta(GenericMeta): @_tp_cache def __getitem__(self, parameters): - if self.__origin__ is not None or not _geqv(self, Tuple): + if self.__origin__ is not None or self._gorg is not Tuple: # Normal generic rules apply if this is not the first subscription # or a subscription of a subclass. return super(TupleMeta, self).__getitem__(parameters) @@ -1411,7 +1390,7 @@ class Tuple(tuple): __slots__ = () def __new__(cls, *args, **kwds): - if _geqv(cls, Tuple): + if cls._gorg is Tuple: raise TypeError("Type Tuple cannot be instantiated; " "use tuple() instead") return _generic_new(tuple, cls, *args, **kwds) @@ -1426,7 +1405,7 @@ def __repr__(self): return self._tree_repr(self._subs_tree()) def _tree_repr(self, tree): - if _gorg(self) is not Callable: + if self._gorg is not Callable: return super(CallableMeta, self)._tree_repr(tree) # For actual Callable (not its subclass) we override # super(CallableMeta, self)._tree_repr() for nice formatting. @@ -1446,7 +1425,7 @@ def __getitem__(self, parameters): with hashable arguments to improve speed. """ - if self.__origin__ is not None or not _geqv(self, Callable): + if self.__origin__ is not None or self._gorg is not Callable: return super(CallableMeta, self).__getitem__(parameters) if not isinstance(parameters, tuple) or len(parameters) != 2: raise TypeError("Callable must be used as " @@ -1490,7 +1469,7 @@ class Callable(object): __slots__ = () def __new__(cls, *args, **kwds): - if _geqv(cls, Callable): + if cls._gorg is Callable: raise TypeError("Type Callable cannot be instantiated; " "use a non-abstract subclass instead") return _generic_new(cls.__next_in_mro__, cls, *args, **kwds) @@ -1808,7 +1787,7 @@ class List(list, MutableSequence[T]): __extra__ = list def __new__(cls, *args, **kwds): - if _geqv(cls, List): + if cls._gorg is List: raise TypeError("Type List cannot be instantiated; " "use list() instead") return _generic_new(list, cls, *args, **kwds) @@ -1819,7 +1798,7 @@ class Deque(collections.deque, MutableSequence[T]): __extra__ = collections.deque def __new__(cls, *args, **kwds): - if _geqv(cls, Deque): + if cls._gorg is Deque: return collections.deque(*args, **kwds) return _generic_new(collections.deque, cls, *args, **kwds) @@ -1829,7 +1808,7 @@ class Set(set, MutableSet[T]): __extra__ = set def __new__(cls, *args, **kwds): - if _geqv(cls, Set): + if cls._gorg is Set: raise TypeError("Type Set cannot be instantiated; " "use set() instead") return _generic_new(set, cls, *args, **kwds) @@ -1840,7 +1819,7 @@ class FrozenSet(frozenset, AbstractSet[T_co]): __extra__ = frozenset def __new__(cls, *args, **kwds): - if _geqv(cls, FrozenSet): + if cls._gorg is FrozenSet: raise TypeError("Type FrozenSet cannot be instantiated; " "use frozenset() instead") return _generic_new(frozenset, cls, *args, **kwds) @@ -1897,7 +1876,7 @@ class Dict(dict, MutableMapping[KT, VT]): __extra__ = dict def __new__(cls, *args, **kwds): - if _geqv(cls, Dict): + if cls._gorg is Dict: raise TypeError("Type Dict cannot be instantiated; " "use dict() instead") return _generic_new(dict, cls, *args, **kwds) @@ -1908,7 +1887,7 @@ class DefaultDict(collections.defaultdict, MutableMapping[KT, VT]): __extra__ = collections.defaultdict def __new__(cls, *args, **kwds): - if _geqv(cls, DefaultDict): + if cls._gorg is DefaultDict: return collections.defaultdict(*args, **kwds) return _generic_new(collections.defaultdict, cls, *args, **kwds) @@ -1918,7 +1897,7 @@ class Counter(collections.Counter, Dict[T, int]): __extra__ = collections.Counter def __new__(cls, *args, **kwds): - if _geqv(cls, Counter): + if cls._gorg is Counter: return collections.Counter(*args, **kwds) return _generic_new(collections.Counter, cls, *args, **kwds) @@ -1937,7 +1916,7 @@ class Generator(Iterator[T_co], Generic[T_co, T_contra, V_co]): __extra__ = _G_base def __new__(cls, *args, **kwds): - if _geqv(cls, Generator): + if cls._gorg is Generator: raise TypeError("Type Generator cannot be instantiated; " "create a subclass instead") return _generic_new(_G_base, cls, *args, **kwds) From 995572fcd204afb2cd8e194e3a114abb8c096aa5 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 12 Jun 2017 18:08:47 +0200 Subject: [PATCH 12/14] Keep only obvious speed-ups and drop more aggressive optiomizations --- python2/typing.py | 28 +++++++++------------------- src/typing.py | 26 +++++++------------------- 2 files changed, 16 insertions(+), 38 deletions(-) diff --git a/python2/typing.py b/python2/typing.py index 836f028f7..ca18d1b49 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -1051,25 +1051,15 @@ def __new__(cls, name, bases, namespace, tvars = gvars initial_bases = bases - if not origin: - # Erase base classes - new_bases = [] - for base in bases: - erased = base - if isinstance(base, GenericMeta): - erased = base._gorg - if extra: # Even stronger erasure for generic ABCs in typing - bextra = getattr(base, '__extra__', None) - erased = bextra if bextra else erased - new_bases.append(erased) - - bases = tuple(new_bases) - if type(extra) is abc.ABCMeta and extra not in bases: - bases = (extra,) + bases - - # Remove bare Generic from bases if there are other generic bases - if any(isinstance(b, GenericMeta) and b is not Generic for b in bases): - bases = tuple(b for b in bases if b is not Generic) + if extra is None: + extra = namespace.get('__extra__') + if extra is not None and type(extra) is abc.ABCMeta and extra not in bases: + bases = (extra,) + bases + bases = tuple(b._gorg if isinstance(b, GenericMeta) else b for b in bases) + + # remove bare Generic from bases if there are other generic bases + if any(isinstance(b, GenericMeta) and b is not Generic for b in bases): + bases = tuple(b for b in bases if b is not Generic) namespace.update({'__origin__': origin, '__extra__': extra}) self = super(GenericMeta, cls).__new__(cls, name, bases, namespace) super(GenericMeta, self).__setattr__('_gorg', self if not origin diff --git a/src/typing.py b/src/typing.py index 30794758e..142a51e36 100644 --- a/src/typing.py +++ b/src/typing.py @@ -966,25 +966,13 @@ def __new__(cls, name, bases, namespace, tvars = gvars initial_bases = bases - if not origin: - # Erase base classes - new_bases = [] - for base in bases: - erased = base - if isinstance(base, GenericMeta): - erased = base._gorg - if extra: # Even stronger erasure for generic ABCs in typing - bextra = getattr(base, '__extra__', None) - erased = bextra if bextra else erased - new_bases.append(erased) - - bases = tuple(new_bases) - if type(extra) is abc.ABCMeta and extra not in bases: - bases = (extra,) + bases - - # Remove bare Generic from bases if there are other generic bases - if any(isinstance(b, GenericMeta) and b is not Generic for b in bases): - bases = tuple(b for b in bases if b is not Generic) + if extra is not None and type(extra) is abc.ABCMeta and extra not in bases: + bases = (extra,) + bases + bases = tuple(b._gorg if isinstance(b, GenericMeta) else b for b in bases) + + # remove bare Generic from bases if there are other generic bases + if any(isinstance(b, GenericMeta) and b is not Generic for b in bases): + bases = tuple(b for b in bases if b is not Generic) namespace.update({'__origin__': origin, '__extra__': extra}) self = super().__new__(cls, name, bases, namespace, _root=True) super(GenericMeta, self).__setattr__('_gorg', self if not origin From 9d54a4a032bf9167736449cda82b677d10aa3ff0 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 12 Jun 2017 18:12:55 +0200 Subject: [PATCH 13/14] Fix _Protocol on Python 2 --- python2/typing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/python2/typing.py b/python2/typing.py index ca18d1b49..f647baa71 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -1626,6 +1626,7 @@ def _get_protocol_attrs(self): if (not attr.startswith('_abc_') and attr != '__abstractmethods__' and attr != '_is_protocol' and + attr != '_gorg' and attr != '__dict__' and attr != '__args__' and attr != '__slots__' and From 6b223a1d2890abb4b457c42a40d9637e25300286 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 12 Jun 2017 18:46:13 +0200 Subject: [PATCH 14/14] Address CR --- python2/typing.py | 6 +++--- src/typing.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/python2/typing.py b/python2/typing.py index f647baa71..bbdba2cf9 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -1062,8 +1062,8 @@ def __new__(cls, name, bases, namespace, bases = tuple(b for b in bases if b is not Generic) namespace.update({'__origin__': origin, '__extra__': extra}) self = super(GenericMeta, cls).__new__(cls, name, bases, namespace) - super(GenericMeta, self).__setattr__('_gorg', self if not origin - else origin._gorg) + super(GenericMeta, self).__setattr__('_gorg', + self if not origin else origin._gorg) self.__parameters__ = tvars # Be prepared that GenericMeta will be subclassed by TupleMeta @@ -1509,7 +1509,7 @@ def no_type_check(arg): if isinstance(arg, type): arg_attrs = arg.__dict__.copy() for attr, val in arg.__dict__.items(): - if val in arg.__bases__: + if val in arg.__bases__ + (arg,): arg_attrs.pop(attr) for obj in arg_attrs.values(): if isinstance(obj, types.FunctionType): diff --git a/src/typing.py b/src/typing.py index 142a51e36..609f813b0 100644 --- a/src/typing.py +++ b/src/typing.py @@ -975,8 +975,8 @@ def __new__(cls, name, bases, namespace, bases = tuple(b for b in bases if b is not Generic) namespace.update({'__origin__': origin, '__extra__': extra}) self = super().__new__(cls, name, bases, namespace, _root=True) - super(GenericMeta, self).__setattr__('_gorg', self if not origin - else origin._gorg) + super(GenericMeta, self).__setattr__('_gorg', + self if not origin else origin._gorg) self.__parameters__ = tvars # Be prepared that GenericMeta will be subclassed by TupleMeta # and CallableMeta, those two allow ..., (), or [] in __args___.