From e8c3ad6eebd4942e99d2fe0aef7f99e1e2c854e7 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 23 Oct 2016 12:57:38 +0200 Subject: [PATCH 01/39] Make Tuple[T, S] subcriptable and subclassable --- src/typing.py | 372 ++++++++++++++++++++++++-------------------------- 1 file changed, 182 insertions(+), 190 deletions(-) diff --git a/src/typing.py b/src/typing.py index 261da5d5..b5ecb226 100644 --- a/src/typing.py +++ b/src/typing.py @@ -371,6 +371,8 @@ def _type_repr(obj): return _qualname(obj) else: return '%s.%s' % (obj.__module__, _qualname(obj)) + elif obj is Ellipsis: + return('...') else: return repr(obj) @@ -677,195 +679,6 @@ def __getitem__(self, arg): Optional = _Optional(_root=True) -class _Tuple(_FinalTypingBase, _root=True): - """Tuple type; Tuple[X, Y] is the cross-product type of X and Y. - - Example: Tuple[T1, T2] is a tuple of two elements corresponding - to type variables T1 and T2. Tuple[int, float, str] is a tuple - of an int, a float and a string. - - To specify a variable-length tuple of homogeneous type, use Tuple[T, ...]. - """ - - __slots__ = ('__tuple_params__', '__tuple_use_ellipsis__') - - def __init__(self, parameters=None, - use_ellipsis=False, _root=False): - self.__tuple_params__ = parameters - self.__tuple_use_ellipsis__ = use_ellipsis - - def _get_type_vars(self, tvars): - if self.__tuple_params__: - _get_type_vars(self.__tuple_params__, tvars) - - def _eval_type(self, globalns, localns): - tp = self.__tuple_params__ - if tp is None: - return self - p = tuple(_eval_type(t, globalns, localns) for t in tp) - if p == self.__tuple_params__: - return self - else: - return self.__class__(p, _root=True) - - def __repr__(self): - return self._subs_repr([], []) - - def _subs_repr(self, tvars, args): - r = super().__repr__() - if self.__tuple_params__ is not None: - params = [_replace_arg(p, tvars, args) for p in self.__tuple_params__] - if self.__tuple_use_ellipsis__: - params.append('...') - if not params: - params.append('()') - r += '[%s]' % ( - ', '.join(params)) - return r - - @_tp_cache - def __getitem__(self, parameters): - if self.__tuple_params__ is not None: - raise TypeError("Cannot re-parameterize %r" % (self,)) - if not isinstance(parameters, tuple): - parameters = (parameters,) - if len(parameters) == 2 and parameters[1] == Ellipsis: - parameters = parameters[:1] - use_ellipsis = True - msg = "Tuple[t, ...]: t must be a type." - else: - use_ellipsis = False - msg = "Tuple[t0, t1, ...]: each t must be a type." - parameters = tuple(_type_check(p, msg) for p in parameters) - return self.__class__(parameters, - use_ellipsis=use_ellipsis, _root=True) - - def __eq__(self, other): - if not isinstance(other, _Tuple): - return NotImplemented - return (self.__tuple_params__ == other.__tuple_params__ and - self.__tuple_use_ellipsis__ == other.__tuple_use_ellipsis__) - - def __hash__(self): - return hash((self.__tuple_params__, self.__tuple_use_ellipsis__)) - - def __instancecheck__(self, obj): - if self.__tuple_params__ == None: - return isinstance(obj, tuple) - raise TypeError("Parameterized Tuple cannot be used " - "with isinstance().") - - def __subclasscheck__(self, cls): - if self.__tuple_params__ == None: - return issubclass(cls, tuple) - raise TypeError("Parameterized Tuple cannot be used " - "with issubclass().") - - -Tuple = _Tuple(_root=True) - - -class _Callable(_FinalTypingBase, _root=True): - """Callable type; Callable[[int], str] is a function of (int) -> str. - - The subscription syntax must always be used with exactly two - values: the argument list and the return type. The argument list - must be a list of types; the return type must be a single type. - - There is no syntax to indicate optional or keyword arguments, - such function types are rarely used as callback types. - """ - - __slots__ = ('__args__', '__result__') - - def __init__(self, args=None, result=None, _root=False): - if args is None and result is None: - pass - else: - if args is not Ellipsis: - if not isinstance(args, list): - raise TypeError("Callable[args, result]: " - "args must be a list." - " Got %.100r." % (args,)) - msg = "Callable[[arg, ...], result]: each arg must be a type." - args = tuple(_type_check(arg, msg) for arg in args) - msg = "Callable[args, result]: result must be a type." - result = _type_check(result, msg) - self.__args__ = args - self.__result__ = result - - def _get_type_vars(self, tvars): - if self.__args__ and self.__args__ is not Ellipsis: - _get_type_vars(self.__args__, tvars) - if self.__result__: - _get_type_vars([self.__result__], tvars) - - def _eval_type(self, globalns, localns): - if self.__args__ is None and self.__result__ is None: - return self - if self.__args__ is Ellipsis: - args = self.__args__ - else: - args = [_eval_type(t, globalns, localns) for t in self.__args__] - result = _eval_type(self.__result__, globalns, localns) - if args == self.__args__ and result == self.__result__: - return self - else: - return self.__class__(args, result, _root=True) - - def __repr__(self): - return self._subs_repr([], []) - - def _subs_repr(self, tvars, args): - r = super().__repr__() - if self.__args__ is not None or self.__result__ is not None: - if self.__args__ is Ellipsis: - args_r = '...' - else: - args_r = '[%s]' % ', '.join(_replace_arg(t, tvars, args) - for t in self.__args__) - r += '[%s, %s]' % (args_r, _replace_arg(self.__result__, tvars, args)) - return r - - def __getitem__(self, parameters): - if self.__args__ is not None or self.__result__ is not None: - raise TypeError("This Callable type is already parameterized.") - if not isinstance(parameters, tuple) or len(parameters) != 2: - raise TypeError( - "Callable must be used as Callable[[arg, ...], result].") - args, result = parameters - return self.__class__(args, result, _root=True) - - def __eq__(self, other): - if not isinstance(other, _Callable): - return NotImplemented - return (self.__args__ == other.__args__ and - self.__result__ == other.__result__) - - def __hash__(self): - return hash(self.__args__) ^ hash(self.__result__) - - def __instancecheck__(self, obj): - # For unparametrized Callable we allow this, because - # typing.Callable should be equivalent to - # collections.abc.Callable. - if self.__args__ is None and self.__result__ is None: - return isinstance(obj, collections_abc.Callable) - else: - raise TypeError("Parameterized Callable cannot be used " - "with isinstance().") - - def __subclasscheck__(self, cls): - if self.__args__ is None and self.__result__ is None: - return issubclass(cls, collections_abc.Callable) - else: - raise TypeError("Parameterized Callable cannot be used " - "with issubclass().") - - -Callable = _Callable(_root=True) - - def _gorg(a): """Return the farthest origin of a generic class.""" assert isinstance(a, GenericMeta) @@ -1035,6 +848,18 @@ def _get_type_vars(self, tvars): if self.__origin__ and self.__parameters__: _get_type_vars(self.__parameters__, tvars) + def _eval_type(self, globalns, localns): + return self.__class__(self.__name__, + self.__bases__, + dict(self.__dict__), + tvars=self.__parameters__ if self.__origin__ else None, + args=tuple(_eval_type(a, globalns, localns) for a + in self.__args__) if self.__args__ else None, + origin=(self.__origin__._eval_type(globalns, localns) + if self.__origin__ else None), + extra=self.__extra__, + orig_bases=self.__orig_bases__) + def __repr__(self): if self.__origin__ is None: return super().__repr__() @@ -1077,7 +902,7 @@ def __hash__(self): def __getitem__(self, params): if not isinstance(params, tuple): params = (params,) - if not params: + if not params and not _gorg(self) is Tuple: raise TypeError( "Parameter list to %s[...] cannot be empty" % _qualname(self)) msg = "Parameters to generic types must be types." @@ -1092,6 +917,9 @@ def __getitem__(self, params): "Parameters to Generic[...] must all be unique") tvars = params args = params + elif self is Tuple: + tvars = _type_vars(params) + args = params elif self is _Protocol: # _Protocol is internal, don't check anything. tvars = params @@ -1171,6 +999,170 @@ def __new__(cls, *args, **kwds): return obj +class TupleMeta(GenericMeta): + """Metaclass for Tuple""" + + @_tp_cache + def __getitem__(self, parameters): + if not isinstance(parameters, tuple): + parameters = (parameters,) + if len(parameters) == 2 and parameters[1] == Ellipsis: + if self.__origin__ is not None: + raise TypeError("Cannot use Ellipsis for already subscripted Tuple") + use_ellipsis = True + parameters = parameters[:1] + msg = "Tuple[t, ...]: t must be a type." + else: + use_ellipsis = False + msg = "Tuple[t0, t1, ...]: each t must be a type." + parameters = tuple(_type_check(p, msg) for p in parameters) + if len(parameters) == 0 and self.__origin__ is not None: + raise TypeError("Cannot use () for already subscripted Tuple") + result = super().__getitem__(parameters) + if use_ellipsis: + result.__args__ = result.__args__ + (...,) + if parameters == (): + result.__args__ = ((),) + return result + + def __instancecheck__(self, obj): + if self.__args__ == None: + return isinstance(obj, tuple) + raise TypeError("Parameterized Tuple cannot be used " + "with isinstance().") + + def __subclasscheck__(self, cls): + if self.__args__ == None: + return issubclass(cls, tuple) + raise TypeError("Parameterized Tuple cannot be used " + "with issubclass().") + + def __repr__(self): + if self.__args__ == ((),): + return repr(self.__origin__) + '[()]' + return super().__repr__() + + +class Tuple(tuple, metaclass=TupleMeta): + """Tuple type; Tuple[X, Y] is the cross-product type of X and Y. + + Example: Tuple[T1, T2] is a tuple of two elements corresponding + to type variables T1 and T2. Tuple[int, float, str] is a tuple + of an int, a float and a string. + + To specify a variable-length tuple of homogeneous type, use Tuple[T, ...]. + """ + + __slots__ = () + + def __new__(cls, *args, **kwds): + if _geqv(cls, Tuple): + raise TypeError("Type Tuple cannot be instantiated; " + "use tuple() instead") + return super().__new__(cls, *args, **kwds) + + +class _Callable(_FinalTypingBase, _root=True): + """Callable type; Callable[[int], str] is a function of (int) -> str. + + The subscription syntax must always be used with exactly two + values: the argument list and the return type. The argument list + must be a list of types; the return type must be a single type. + + There is no syntax to indicate optional or keyword arguments, + such function types are rarely used as callback types. + """ + + __slots__ = ('__args__', '__result__') + + def __init__(self, args=None, result=None, _root=False): + if args is None and result is None: + pass + else: + if args is not Ellipsis: + if not isinstance(args, list): + raise TypeError("Callable[args, result]: " + "args must be a list." + " Got %.100r." % (args,)) + msg = "Callable[[arg, ...], result]: each arg must be a type." + args = tuple(_type_check(arg, msg) for arg in args) + msg = "Callable[args, result]: result must be a type." + result = _type_check(result, msg) + self.__args__ = args + self.__result__ = result + + def _get_type_vars(self, tvars): + if self.__args__ and self.__args__ is not Ellipsis: + _get_type_vars(self.__args__, tvars) + if self.__result__: + _get_type_vars([self.__result__], tvars) + + def _eval_type(self, globalns, localns): + if self.__args__ is None and self.__result__ is None: + return self + if self.__args__ is Ellipsis: + args = self.__args__ + else: + args = [_eval_type(t, globalns, localns) for t in self.__args__] + result = _eval_type(self.__result__, globalns, localns) + if args == self.__args__ and result == self.__result__: + return self + else: + return self.__class__(args, result, _root=True) + + def __repr__(self): + return self._subs_repr([], []) + + def _subs_repr(self, tvars, args): + r = super().__repr__() + if self.__args__ is not None or self.__result__ is not None: + if self.__args__ is Ellipsis: + args_r = '...' + else: + args_r = '[%s]' % ', '.join(_replace_arg(t, tvars, args) + for t in self.__args__) + r += '[%s, %s]' % (args_r, _replace_arg(self.__result__, tvars, args)) + return r + + def __getitem__(self, parameters): + if self.__args__ is not None or self.__result__ is not None: + raise TypeError("This Callable type is already parameterized.") + if not isinstance(parameters, tuple) or len(parameters) != 2: + raise TypeError( + "Callable must be used as Callable[[arg, ...], result].") + args, result = parameters + return self.__class__(args, result, _root=True) + + def __eq__(self, other): + if not isinstance(other, _Callable): + return NotImplemented + return (self.__args__ == other.__args__ and + self.__result__ == other.__result__) + + def __hash__(self): + return hash(self.__args__) ^ hash(self.__result__) + + def __instancecheck__(self, obj): + # For unparametrized Callable we allow this, because + # typing.Callable should be equivalent to + # collections.abc.Callable. + if self.__args__ is None and self.__result__ is None: + return isinstance(obj, collections_abc.Callable) + else: + raise TypeError("Parameterized Callable cannot be used " + "with isinstance().") + + def __subclasscheck__(self, cls): + if self.__args__ is None and self.__result__ is None: + return issubclass(cls, collections_abc.Callable) + else: + raise TypeError("Parameterized Callable cannot be used " + "with issubclass().") + + +Callable = _Callable(_root=True) + + class _ClassVar(_FinalTypingBase, _root=True): """Special type construct to mark class variables. From cfcf81f39d0083151eeb90cbd20a854cbf13292f Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 23 Oct 2016 16:17:22 +0200 Subject: [PATCH 02/39] Make Callable[..., T] subscriptable and subclassable --- src/typing.py | 319 ++++++++++++++++++++++++-------------------------- 1 file changed, 153 insertions(+), 166 deletions(-) diff --git a/src/typing.py b/src/typing.py index b5ecb226..c8d9fd6b 100644 --- a/src/typing.py +++ b/src/typing.py @@ -333,8 +333,7 @@ def _type_vars(types): def _eval_type(t, globalns, localns): if isinstance(t, TypingMeta) or isinstance(t, _TypingBase): return t._eval_type(globalns, localns) - else: - return t + return t def _type_check(arg, msg): @@ -353,7 +352,8 @@ def _type_check(arg, msg): return type(None) if isinstance(arg, str): arg = _ForwardRef(arg) - if not isinstance(arg, (type, _TypingBase)) and not callable(arg): + if (isinstance(arg, _ClassVar) or + not isinstance(arg, (type, _TypingBase)) and not callable(arg)): raise TypeError(msg + " Got %.100r." % (arg,)) return arg @@ -369,12 +369,69 @@ def _type_repr(obj): if isinstance(obj, type) and not isinstance(obj, TypingMeta): if obj.__module__ == 'builtins': return _qualname(obj) - else: - return '%s.%s' % (obj.__module__, _qualname(obj)) - elif obj is Ellipsis: + return '%s.%s' % (obj.__module__, _qualname(obj)) + if obj is Ellipsis: return('...') - else: - return repr(obj) + if isinstance(obj, types.FunctionType): + return obj.__name__ + return repr(obj) + + +class _ClassVar(_FinalTypingBase, _root=True): + """Special type construct to mark class variables. + + An annotation wrapped in ClassVar indicates that a given + attribute is intended to be used as a class variable and + should not be set on instances of that class. Usage:: + + class Starship: + stats: ClassVar[Dict[str, int]] = {} # class variable + damage: int = 10 # instance variable + + ClassVar accepts only types and cannot be further subscribed. + + Note that ClassVar is not a class itself, and should not + be used with isinstance() or issubclass(). + """ + + __slots__ = ('__type__',) + + def __init__(self, tp=None, **kwds): + self.__type__ = tp + + def __getitem__(self, item): + cls = type(self) + if self.__type__ is None: + return cls(_type_check(item, + '{} accepts only single type.'.format(cls.__name__[1:])), + _root=True) + raise TypeError('{} cannot be further subscripted' + .format(cls.__name__[1:])) + + def _eval_type(self, globalns, localns): + new_tp = _eval_type(self.__type__, globalns, localns) + if new_tp == self.__type__: + return self + return type(self)(new_tp, _root=True) + + def __repr__(self): + r = super().__repr__() + if self.__type__ is not None: + r += '[{}]'.format(_type_repr(self.__type__)) + return r + + def __hash__(self): + return hash((type(self).__name__, self.__type__)) + + def __eq__(self, other): + if not isinstance(other, _ClassVar): + return NotImplemented + if self.__type__ is not None: + return self.__type__ == other.__type__ + return self is other + + +ClassVar = _ClassVar(_root=True) class _Any(_FinalTypingBase, _root=True): @@ -865,7 +922,7 @@ def __repr__(self): return super().__repr__() return self._subs_repr([], []) - def _subs_repr(self, tvars, args): + def _subs_repr(self, tvars, args, *, for_callable=False): assert len(tvars) == len(args) # Construct the chain of __origin__'s. current = self.__origin__ @@ -883,7 +940,16 @@ def _subs_repr(self, tvars, args): for i, arg in enumerate(cls.__args__): new_str_args.append(_replace_arg(arg, cls.__parameters__, str_args)) str_args = new_str_args - return super().__repr__() + '[%s]' % ', '.join(str_args) + if not for_callable: + return super().__repr__() + '[%s]' % ', '.join(str_args) + else: + if str_args[0] == '...': + return super().__repr__() + '[..., %s]' % str_args[1] + elif str_args[0] == '()': + return super().__repr__() + '[[], %s]' % str_args[1] + else: + return (super().__repr__() + + '[[%s], %s]' % (', '.join(str_args[:-1]), str_args[-1])) def __eq__(self, other): if not isinstance(other, GenericMeta): @@ -917,7 +983,7 @@ def __getitem__(self, params): "Parameters to Generic[...] must all be unique") tvars = params args = params - elif self is Tuple: + elif self in (Tuple, Callable): tvars = _type_vars(params) args = params elif self is _Protocol: @@ -999,29 +1065,40 @@ def __new__(cls, *args, **kwds): return obj +class _TypingEllipsis: + """Placeholder for ...""" + + +class _TypingEmpty: + """Placeholder for ()""" + + class TupleMeta(GenericMeta): """Metaclass for Tuple""" @_tp_cache def __getitem__(self, parameters): + if self.__origin__ is not None or not _geqv(self, Tuple): + return super().__getitem__(parameters) if not isinstance(parameters, tuple): parameters = (parameters,) if len(parameters) == 2 and parameters[1] == Ellipsis: - if self.__origin__ is not None: - raise TypeError("Cannot use Ellipsis for already subscripted Tuple") use_ellipsis = True - parameters = parameters[:1] + parameters = parameters[:1] + (_TypingEllipsis,) msg = "Tuple[t, ...]: t must be a type." else: use_ellipsis = False msg = "Tuple[t0, t1, ...]: each t must be a type." parameters = tuple(_type_check(p, msg) for p in parameters) - if len(parameters) == 0 and self.__origin__ is not None: - raise TypeError("Cannot use () for already subscripted Tuple") + if parameters == (): + empty = True + parameters = (_TypingEmpty,) + else: + empty = False result = super().__getitem__(parameters) if use_ellipsis: - result.__args__ = result.__args__ + (...,) - if parameters == (): + result.__args__ = result.__args__[:-1] + (...,) + elif empty: result.__args__ = ((),) return result @@ -1039,11 +1116,11 @@ def __subclasscheck__(self, cls): def __repr__(self): if self.__args__ == ((),): - return repr(self.__origin__) + '[()]' + return repr(Tuple) + '[()]' return super().__repr__() -class Tuple(tuple, metaclass=TupleMeta): +class Tuple(tuple, extra=tuple, metaclass=TupleMeta): """Tuple type; Tuple[X, Y] is the cross-product type of X and Y. Example: Tuple[T1, T2] is a tuple of two elements corresponding @@ -1062,168 +1139,78 @@ def __new__(cls, *args, **kwds): return super().__new__(cls, *args, **kwds) -class _Callable(_FinalTypingBase, _root=True): - """Callable type; Callable[[int], str] is a function of (int) -> str. - - The subscription syntax must always be used with exactly two - values: the argument list and the return type. The argument list - must be a list of types; the return type must be a single type. - - There is no syntax to indicate optional or keyword arguments, - such function types are rarely used as callback types. - """ - - __slots__ = ('__args__', '__result__') - - def __init__(self, args=None, result=None, _root=False): - if args is None and result is None: - pass - else: - if args is not Ellipsis: - if not isinstance(args, list): - raise TypeError("Callable[args, result]: " - "args must be a list." - " Got %.100r." % (args,)) - msg = "Callable[[arg, ...], result]: each arg must be a type." - args = tuple(_type_check(arg, msg) for arg in args) - msg = "Callable[args, result]: result must be a type." - result = _type_check(result, msg) - self.__args__ = args - self.__result__ = result - - def _get_type_vars(self, tvars): - if self.__args__ and self.__args__ is not Ellipsis: - _get_type_vars(self.__args__, tvars) - if self.__result__: - _get_type_vars([self.__result__], tvars) - - def _eval_type(self, globalns, localns): - if self.__args__ is None and self.__result__ is None: - return self - if self.__args__ is Ellipsis: - args = self.__args__ - else: - args = [_eval_type(t, globalns, localns) for t in self.__args__] - result = _eval_type(self.__result__, globalns, localns) - if args == self.__args__ and result == self.__result__: - return self - else: - return self.__class__(args, result, _root=True) +class CallableMeta(GenericMeta): def __repr__(self): - return self._subs_repr([], []) - - def _subs_repr(self, tvars, args): - r = super().__repr__() - if self.__args__ is not None or self.__result__ is not None: - if self.__args__ is Ellipsis: - args_r = '...' - else: - args_r = '[%s]' % ', '.join(_replace_arg(t, tvars, args) - for t in self.__args__) - r += '[%s, %s]' % (args_r, _replace_arg(self.__result__, tvars, args)) - return r + if self.__origin__ is None: + return super().__repr__() + return super()._subs_repr([], [], for_callable=True) def __getitem__(self, parameters): - if self.__args__ is not None or self.__result__ is not None: - raise TypeError("This Callable type is already parameterized.") + if self.__origin__ is not None or not _geqv(self, Callable): + return super().__getitem__(parameters) if not isinstance(parameters, tuple) or len(parameters) != 2: - raise TypeError( - "Callable must be used as Callable[[arg, ...], result].") + raise TypeError("Callable must be used as " + "Callable[[arg, ...], result].") args, result = parameters - return self.__class__(args, result, _root=True) - - def __eq__(self, other): - if not isinstance(other, _Callable): - return NotImplemented - return (self.__args__ == other.__args__ and - self.__result__ == other.__result__) - - def __hash__(self): - return hash(self.__args__) ^ hash(self.__result__) - - def __instancecheck__(self, obj): - # For unparametrized Callable we allow this, because - # typing.Callable should be equivalent to - # collections.abc.Callable. - if self.__args__ is None and self.__result__ is None: - return isinstance(obj, collections_abc.Callable) + if args is not Ellipsis: + if not isinstance(args, list): + raise TypeError("Callable[args, result]: " + "args must be a list." + " Got %.100r." % (args,)) + if args is Ellipsis: + parameters = (Ellipsis, result) + elif args == []: + parameters = ((), result) else: - raise TypeError("Parameterized Callable cannot be used " - "with isinstance().") + parameters = tuple(args) + (result,) + return self.__getitem_inner__(parameters) - def __subclasscheck__(self, cls): - if self.__args__ is None and self.__result__ is None: - return issubclass(cls, collections_abc.Callable) + @_tp_cache + def __getitem_inner__(self, parameters): + args = parameters[:-1] + result = parameters[-1] + msg = "Callable[args, result]: result must be a type." + result = _type_check(result, msg) + if args != (Ellipsis,): + use_ellipsis = False + if args != ((),): + empty_args = False + msg = "Callable[[arg, ...], result]: each arg must be a type." + args = tuple(_type_check(arg, msg) for arg in args) + else: + empty_args = True + args = (_TypingEmpty,) else: - raise TypeError("Parameterized Callable cannot be used " - "with issubclass().") - - -Callable = _Callable(_root=True) - + use_ellipsis = True + args = (_TypingEllipsis,) + parameters = args + (result,) + result = super().__getitem__(parameters) + if use_ellipsis: + result.__args__ = (...,) + result.__args__[1:] + elif empty_args: + result.__args__ = ((),) + result.__args__[1:] + return result -class _ClassVar(_FinalTypingBase, _root=True): - """Special type construct to mark class variables. - An annotation wrapped in ClassVar indicates that a given - attribute is intended to be used as a class variable and - should not be set on instances of that class. Usage:: - - class Starship: - stats: ClassVar[Dict[str, int]] = {} # class variable - damage: int = 10 # instance variable +class Callable(extra=collections_abc.Callable, metaclass = CallableMeta): + """Callable type; Callable[[int], str] is a function of (int) -> str. - ClassVar accepts only types and cannot be further subscribed. + The subscription syntax must always be used with exactly two + values: the argument list and the return type. The argument list + must be a list of types; the return type must be a single type. - Note that ClassVar is not a class itself, and should not - be used with isinstance() or issubclass(). + There is no syntax to indicate optional or keyword arguments, + such function types are rarely used as callback types. """ - __slots__ = ('__type__',) - - def __init__(self, tp=None, **kwds): - self.__type__ = tp - - def __getitem__(self, item): - cls = type(self) - if self.__type__ is None: - return cls(_type_check(item, - '{} accepts only single type.'.format(cls.__name__[1:])), - _root=True) - raise TypeError('{} cannot be further subscripted' - .format(cls.__name__[1:])) - - def _eval_type(self, globalns, localns): - new_tp = _eval_type(self.__type__, globalns, localns) - if new_tp == self.__type__: - return self - return type(self)(new_tp, _root=True) - - def _get_type_vars(self, tvars): - if self.__type__: - _get_type_vars([self.__type__], tvars) - - def __repr__(self): - return self._subs_repr([], []) - - def _subs_repr(self, tvars, args): - r = super().__repr__() - if self.__type__ is not None: - r += '[{}]'.format(_replace_arg(self.__type__, tvars, args)) - return r - - def __hash__(self): - return hash((type(self).__name__, self.__type__)) - - def __eq__(self, other): - if not isinstance(other, _ClassVar): - return NotImplemented - if self.__type__ is not None: - return self.__type__ == other.__type__ - return self is other + __slots__ = () -ClassVar = _ClassVar(_root=True) + def __new__(cls, *args, **kwds): + if _geqv(cls, Callable): + raise TypeError("Type Callable cannot be instantiated; " + "use a non-abstract subclass instead") + return super().__new__(cls, *args, **kwds) def cast(typ, val): From 1368e976a20b071bd9dfa9a741c8534330021279 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 23 Oct 2016 17:36:23 +0200 Subject: [PATCH 03/39] Make Union[T, S] subsctiptable --- src/typing.py | 102 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 67 insertions(+), 35 deletions(-) diff --git a/src/typing.py b/src/typing.py index c8d9fd6b..0f256b8f 100644 --- a/src/typing.py +++ b/src/typing.py @@ -619,13 +619,19 @@ class Manager(Employee): pass - You can use Optional[X] as a shorthand for Union[X, None]. """ - __slots__ = ('__union_params__', '__union_set_params__') - - def __new__(cls, parameters=None, *args, _root=False): - self = super().__new__(cls, parameters, *args, _root=_root) - if parameters is None: - self.__union_params__ = None - self.__union_set_params__ = None + __slots__ = ('__parameters__', '__args__', '__origin__') + + def __new__(cls, parameters=None, origin=None, *args, _root=False): + self = super().__new__(cls, parameters, origin, *args, _root=_root) + if origin is None: + self.__parameters__ = None + self.__args__ = None + self.__origin__ = None + return self + if origin is not Union: + self.__parameters__ = _type_vars(parameters) + self.__args__ = parameters + self.__origin__ = origin return self if not isinstance(parameters, tuple): raise TypeError("Expected parameters=") @@ -634,7 +640,7 @@ def __new__(cls, parameters=None, *args, _root=False): msg = "Union[arg, ...]: each arg must be a type." for p in parameters: if isinstance(p, _Union): - params.extend(p.__union_params__) + params.extend(p.__args__) else: params.append(_type_check(p, msg)) # Weed out strict duplicates, preserving the first of each occurrence. @@ -664,50 +670,76 @@ def __new__(cls, parameters=None, *args, _root=False): # It's not a union if there's only one type left. if len(all_params) == 1: return all_params.pop() - self.__union_params__ = tuple(t for t in params if t in all_params) - self.__union_set_params__ = frozenset(self.__union_params__) + self.__origin__ = origin + self.__args__ = tuple(t for t in params if t in all_params) + self.__parameters__ = _type_vars(self.__args__) return self def _eval_type(self, globalns, localns): - p = tuple(_eval_type(t, globalns, localns) - for t in self.__union_params__) - if p == self.__union_params__: + if self.__args__ is None: return self - else: - return self.__class__(p, _root=True) + p = tuple(_eval_type(t, globalns, localns) + for t in self.__args__) + return self.__class__(p, + _eval_type(self.__origin__, globalns, localns) + if self.__origin__ is not None else None, + _root=True) def _get_type_vars(self, tvars): - if self.__union_params__: - _get_type_vars(self.__union_params__, tvars) + if self.__origin__ and self.__parameters__: + _get_type_vars(self.__parameters__, tvars) def __repr__(self): + if self.__origin__ is None: + return super().__repr__() return self._subs_repr([], []) def _subs_repr(self, tvars, args): - r = super().__repr__() - if self.__union_params__: - r += '[%s]' % (', '.join(_replace_arg(t, tvars, args) - for t in self.__union_params__)) - return r + assert len(tvars) == len(args) + # Construct the chain of __origin__'s. + if not self.__args__: + return super().__repr__() + current = self.__origin__ + orig_chain = [] + while current.__origin__ is not None: + orig_chain.append(current) + current = current.__origin__ + # Replace type variables in __args__ if asked ... + str_args = [] + for arg in self.__args__: + str_args.append(_replace_arg(arg, tvars, args)) + # ... then continue replacing down the origin chain. + for cls in orig_chain: + new_str_args = [] + for i, arg in enumerate(cls.__args__): + new_str_args.append(_replace_arg(arg, cls.__parameters__, str_args)) + str_args = new_str_args + return super().__repr__() + '[%s]' % ', '.join(str_args) @_tp_cache def __getitem__(self, parameters): - if self.__union_params__ is not None: - raise TypeError( - "Cannot subscript an existing Union. Use Union[u, t] instead.") if parameters == (): raise TypeError("Cannot take a Union of no types.") if not isinstance(parameters, tuple): parameters = (parameters,) - return self.__class__(parameters, _root=True) + if self is not Union: + if not self.__parameters__: + raise TypeError("%s is not a generic class" % repr(self)) + alen = len(parameters) + elen = len(self.__parameters__) + if alen != elen: + raise TypeError( + "Too %s parameters for %s; actual %s, expected %s" % + ("many" if alen > elen else "few", repr(self), alen, elen)) + return self.__class__(parameters, origin=self, _root=True) def __eq__(self, other): if not isinstance(other, _Union): return NotImplemented - return self.__union_set_params__ == other.__union_set_params__ + return frozenset(self.__args__) == frozenset(other.__args__) def __hash__(self): - return hash(self.__union_set_params__) + return hash(frozenset(self.__args__)) def __instancecheck__(self, obj): raise TypeError("Unions cannot be used with isinstance().") @@ -942,14 +974,14 @@ def _subs_repr(self, tvars, args, *, for_callable=False): str_args = new_str_args if not for_callable: return super().__repr__() + '[%s]' % ', '.join(str_args) + # Special format fot Callable + if str_args[0] == '...': + return super().__repr__() + '[..., %s]' % str_args[1] + if str_args[0] == '()': + return super().__repr__() + '[[], %s]' % str_args[1] else: - if str_args[0] == '...': - return super().__repr__() + '[..., %s]' % str_args[1] - elif str_args[0] == '()': - return super().__repr__() + '[[], %s]' % str_args[1] - else: - return (super().__repr__() + - '[[%s], %s]' % (', '.join(str_args[:-1]), str_args[-1])) + return (super().__repr__() + + '[[%s], %s]' % (', '.join(str_args[:-1]), str_args[-1])) def __eq__(self, other): if not isinstance(other, GenericMeta): From 7ee4a1a1cc9d6c26f1432bd2d6552024efc7f75e Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 23 Oct 2016 17:53:18 +0200 Subject: [PATCH 04/39] Disable plain Union and Optional as type arguments --- src/typing.py | 119 ++++++++++++++++++++++++++------------------------ 1 file changed, 61 insertions(+), 58 deletions(-) diff --git a/src/typing.py b/src/typing.py index 0f256b8f..9850cacf 100644 --- a/src/typing.py +++ b/src/typing.py @@ -352,9 +352,12 @@ def _type_check(arg, msg): return type(None) if isinstance(arg, str): arg = _ForwardRef(arg) - if (isinstance(arg, _ClassVar) or + if (isinstance(arg, _TypingBase) and type(arg).__name__ == '_ClassVar' or not isinstance(arg, (type, _TypingBase)) and not callable(arg)): raise TypeError(msg + " Got %.100r." % (arg,)) + if (type(arg).__name__ in ('_Union', '_Optional') + and not getattr(arg, '__origin__', None)): + raise TypeError("Plain %s is not valid as type argument" % arg) return arg @@ -377,63 +380,6 @@ def _type_repr(obj): return repr(obj) -class _ClassVar(_FinalTypingBase, _root=True): - """Special type construct to mark class variables. - - An annotation wrapped in ClassVar indicates that a given - attribute is intended to be used as a class variable and - should not be set on instances of that class. Usage:: - - class Starship: - stats: ClassVar[Dict[str, int]] = {} # class variable - damage: int = 10 # instance variable - - ClassVar accepts only types and cannot be further subscribed. - - Note that ClassVar is not a class itself, and should not - be used with isinstance() or issubclass(). - """ - - __slots__ = ('__type__',) - - def __init__(self, tp=None, **kwds): - self.__type__ = tp - - def __getitem__(self, item): - cls = type(self) - if self.__type__ is None: - return cls(_type_check(item, - '{} accepts only single type.'.format(cls.__name__[1:])), - _root=True) - raise TypeError('{} cannot be further subscripted' - .format(cls.__name__[1:])) - - def _eval_type(self, globalns, localns): - new_tp = _eval_type(self.__type__, globalns, localns) - if new_tp == self.__type__: - return self - return type(self)(new_tp, _root=True) - - def __repr__(self): - r = super().__repr__() - if self.__type__ is not None: - r += '[{}]'.format(_type_repr(self.__type__)) - return r - - def __hash__(self): - return hash((type(self).__name__, self.__type__)) - - def __eq__(self, other): - if not isinstance(other, _ClassVar): - return NotImplemented - if self.__type__ is not None: - return self.__type__ == other.__type__ - return self is other - - -ClassVar = _ClassVar(_root=True) - - class _Any(_FinalTypingBase, _root=True): """Special type indicating an unconstrained type. @@ -1245,6 +1191,63 @@ def __new__(cls, *args, **kwds): return super().__new__(cls, *args, **kwds) +class _ClassVar(_FinalTypingBase, _root=True): + """Special type construct to mark class variables. + + An annotation wrapped in ClassVar indicates that a given + attribute is intended to be used as a class variable and + should not be set on instances of that class. Usage:: + + class Starship: + stats: ClassVar[Dict[str, int]] = {} # class variable + damage: int = 10 # instance variable + + ClassVar accepts only types and cannot be further subscribed. + + Note that ClassVar is not a class itself, and should not + be used with isinstance() or issubclass(). + """ + + __slots__ = ('__type__',) + + def __init__(self, tp=None, **kwds): + self.__type__ = tp + + def __getitem__(self, item): + cls = type(self) + if self.__type__ is None: + return cls(_type_check(item, + '{} accepts only single type.'.format(cls.__name__[1:])), + _root=True) + raise TypeError('{} cannot be further subscripted' + .format(cls.__name__[1:])) + + def _eval_type(self, globalns, localns): + new_tp = _eval_type(self.__type__, globalns, localns) + if new_tp == self.__type__: + return self + return type(self)(new_tp, _root=True) + + def __repr__(self): + r = super().__repr__() + if self.__type__ is not None: + r += '[{}]'.format(_type_repr(self.__type__)) + return r + + def __hash__(self): + return hash((type(self).__name__, self.__type__)) + + def __eq__(self, other): + if not isinstance(other, _ClassVar): + return NotImplemented + if self.__type__ is not None: + return self.__type__ == other.__type__ + return self is other + + +ClassVar = _ClassVar(_root=True) + + def cast(typ, val): """Cast a value to a type. From 5fdceba8144133eef7790e72d6edfd8d9f27b733 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 23 Oct 2016 19:04:00 +0200 Subject: [PATCH 05/39] Implement __eq__ for GenericMeta --- src/typing.py | 80 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 61 insertions(+), 19 deletions(-) diff --git a/src/typing.py b/src/typing.py index 9850cacf..87c573a4 100644 --- a/src/typing.py +++ b/src/typing.py @@ -585,7 +585,7 @@ def __new__(cls, parameters=None, origin=None, *args, _root=False): params = [] msg = "Union[arg, ...]: each arg must be a type." for p in parameters: - if isinstance(p, _Union): + if isinstance(p, _Union) and p.__origin__ is Union: params.extend(p.__args__) else: params.append(_type_check(p, msg)) @@ -641,8 +641,8 @@ def __repr__(self): return self._subs_repr([], []) def _subs_repr(self, tvars, args): - assert len(tvars) == len(args) - # Construct the chain of __origin__'s. + # This is an adapted equivalent of code in GenericMeta. + # Look there for detailed explanations. if not self.__args__: return super().__repr__() current = self.__origin__ @@ -650,11 +650,9 @@ def _subs_repr(self, tvars, args): while current.__origin__ is not None: orig_chain.append(current) current = current.__origin__ - # Replace type variables in __args__ if asked ... str_args = [] for arg in self.__args__: str_args.append(_replace_arg(arg, tvars, args)) - # ... then continue replacing down the origin chain. for cls in orig_chain: new_str_args = [] for i, arg in enumerate(cls.__args__): @@ -747,6 +745,16 @@ def _replace_arg(arg, tvars, args): return _type_repr(arg) +def _replace_arg_nos(arg, tvars, args): + if hasattr(arg, '_subs_tree'): + return arg._subs_tree(tvars, args) + if isinstance(arg, TypeVar): + for i, tvar in enumerate(tvars): + if arg == tvar: + return args[i] + return arg + + def _next_in_mro(cls): """Helper for Generic.__new__. @@ -884,14 +892,22 @@ def _get_type_vars(self, tvars): _get_type_vars(self.__parameters__, tvars) def _eval_type(self, globalns, localns): + ev_origin = (self.__origin__._eval_type(globalns, localns) + if self.__origin__ else None) + ev_args = tuple(_eval_type(a, globalns, localns) for a + in self.__args__) if self.__args__ else None + if ev_origin == self.__origin__ and ev_args == self.__args__: + return self + if ev_origin == self.__origin__: + ev_origin = self.__origin__ + if ev_args == self.__args__: + ev_args = self.__args__ return self.__class__(self.__name__, self.__bases__, dict(self.__dict__), tvars=self.__parameters__ if self.__origin__ else None, - args=tuple(_eval_type(a, globalns, localns) for a - in self.__args__) if self.__args__ else None, - origin=(self.__origin__._eval_type(globalns, localns) - if self.__origin__ else None), + args=ev_args, + origin=ev_origin, extra=self.__extra__, orig_bases=self.__orig_bases__) @@ -929,18 +945,34 @@ def _subs_repr(self, tvars, args, *, for_callable=False): return (super().__repr__() + '[[%s], %s]' % (', '.join(str_args[:-1]), str_args[-1])) + def _subs_tree(self, tvars, args): + current = self.__origin__ + orig_chain = [] + while current.__origin__ is not None: + orig_chain.append(current) + current = current.__origin__ + # Replace type variables in __args__ if asked ... + tree_args = () + for arg in self.__args__: + tree_args += (_replace_arg_nos(arg, tvars, args),) + # ... then continue replacing down the origin chain. + for cls in orig_chain: + new_tree_args = () + for i, arg in enumerate(cls.__args__): + new_tree_args += (_replace_arg_nos(arg, cls.__parameters__, tree_args),) + tree_args = new_tree_args + return ((orig_chain[-1].__origin__ if orig_chain else self.__origin__),) + tree_args + def __eq__(self, other): if not isinstance(other, GenericMeta): return NotImplemented - if self.__origin__ is not None: - return (self.__origin__ is other.__origin__ and - self.__args__ == other.__args__ and - self.__parameters__ == other.__parameters__) + if self.__origin__ is not None and other.__origin__ is not None: + return self._subs_tree([], []) == other._subs_tree([], []) else: return self is other def __hash__(self): - return hash((self.__name__, self.__parameters__)) + return hash((self.__name__, self._subs_tree([], []) if self.__origin__ else ())) @_tp_cache def __getitem__(self, params): @@ -1054,6 +1086,16 @@ class _TypingEmpty: class TupleMeta(GenericMeta): """Metaclass for Tuple""" + def __repr__(self): + if self.__args__ == ((),): + return repr(Tuple) + '[()]' + return super().__repr__() + + def _subs_repr(self, tvars, args): + if not self.__args__: + return super().__repr__() + return super()._subs_repr(tvars, args) + @_tp_cache def __getitem__(self, parameters): if self.__origin__ is not None or not _geqv(self, Tuple): @@ -1092,11 +1134,6 @@ def __subclasscheck__(self, cls): raise TypeError("Parameterized Tuple cannot be used " "with issubclass().") - def __repr__(self): - if self.__args__ == ((),): - return repr(Tuple) + '[()]' - return super().__repr__() - class Tuple(tuple, extra=tuple, metaclass=TupleMeta): """Tuple type; Tuple[X, Y] is the cross-product type of X and Y. @@ -1124,6 +1161,11 @@ def __repr__(self): return super().__repr__() return super()._subs_repr([], [], for_callable=True) + def _subs_repr(self, tvars, args): + if not self.__args__: + return super().__repr__() + return super()._subs_repr(tvars, args) + def __getitem__(self, parameters): if self.__origin__ is not None or not _geqv(self, Callable): return super().__getitem__(parameters) From f3395860a39e858a92615cba26387afb1a88e17f Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 23 Oct 2016 19:48:10 +0200 Subject: [PATCH 06/39] Implement __eq__ for Unions --- src/typing.py | 61 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/src/typing.py b/src/typing.py index 87c573a4..bd2f814a 100644 --- a/src/typing.py +++ b/src/typing.py @@ -507,6 +507,19 @@ def __subclasscheck__(self, cls): AnyStr = TypeVar('AnyStr', bytes, str) +def remove_dups(params): + all_params = set(params) + if len(all_params) < len(params): + new_params = [] + for t in params: + if t in all_params: + new_params.append(t) + all_params.remove(t) + params = new_params + assert not all_params, all_params + return params + + def _tp_cache(func): cached = functools.lru_cache()(func) @functools.wraps(func) @@ -590,15 +603,7 @@ def __new__(cls, parameters=None, origin=None, *args, _root=False): else: params.append(_type_check(p, msg)) # Weed out strict duplicates, preserving the first of each occurrence. - all_params = set(params) - if len(all_params) < len(params): - new_params = [] - for t in params: - if t in all_params: - new_params.append(t) - all_params.remove(t) - params = new_params - assert not all_params, all_params + params = remove_dups(params) # Weed out subclasses. # E.g. Union[int, Employee, Manager] == Union[int, Employee]. # If object is present it will be sole survivor among proper classes. @@ -677,13 +682,42 @@ def __getitem__(self, parameters): ("many" if alen > elen else "few", repr(self), alen, elen)) return self.__class__(parameters, origin=self, _root=True) + def _subs_tree(self, tvars, args): + # This is an adapted equivalent of code in GenericMeta (removing duplicates). + current = self.__origin__ + orig_chain = [] + while current.__origin__ is not None: + orig_chain.append(current) + current = current.__origin__ + tree_args = [] + for arg in self.__args__: + tree_args.append(_replace_arg_nos(arg, tvars, args)) + tree_args = remove_dups(tree_args) + for cls in orig_chain: + new_tree_args = [] + for i, arg in enumerate(cls.__args__): + new_tree_args.append(_replace_arg_nos(arg, cls.__parameters__, + tree_args)) + tree_args = remove_dups(new_tree_args) + res = ((orig_chain[-1].__origin__ if orig_chain + else self.__origin__),) + tuple(tree_args) + if len(res) == 2: + return res[1] + return res + def __eq__(self, other): - if not isinstance(other, _Union): - return NotImplemented - return frozenset(self.__args__) == frozenset(other.__args__) + if isinstance(other, _Union): + if self.__origin__ and other.__origin__: + return (frozenset(self._subs_tree([], [])) + == frozenset(other._subs_tree([], []))) + return self is other + if self.__origin__: + return self._subs_tree([], []) is other + return NotImplemented def __hash__(self): - return hash(frozenset(self.__args__)) + return hash((type(self).__name__, + frozenset(self._subs_tree([], [])) if self.__origin__ else ())) def __instancecheck__(self, obj): raise TypeError("Unions cannot be used with isinstance().") @@ -946,6 +980,7 @@ def _subs_repr(self, tvars, args, *, for_callable=False): '[[%s], %s]' % (', '.join(str_args[:-1]), str_args[-1])) def _subs_tree(self, tvars, args): + # Construct the chain of __origin__'s. current = self.__origin__ orig_chain = [] while current.__origin__ is not None: From 26f50a3b68cf55de825466faa67d01346393861d Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 23 Oct 2016 21:05:23 +0200 Subject: [PATCH 07/39] Refactor common code in __eq__ and __repr__ --- src/typing.py | 123 ++++++++++++++++++++++---------------------------- 1 file changed, 54 insertions(+), 69 deletions(-) diff --git a/src/typing.py b/src/typing.py index bd2f814a..e0f15c31 100644 --- a/src/typing.py +++ b/src/typing.py @@ -587,6 +587,8 @@ def __new__(cls, parameters=None, origin=None, *args, _root=False): self.__args__ = None self.__origin__ = None return self + if not isinstance(parameters, tuple): + raise TypeError("Expected parameters=") if origin is not Union: self.__parameters__ = _type_vars(parameters) self.__args__ = parameters @@ -596,12 +598,11 @@ def __new__(cls, parameters=None, origin=None, *args, _root=False): raise TypeError("Expected parameters=") # Flatten out Union[Union[...], ...] and type-check non-Union args. params = [] - msg = "Union[arg, ...]: each arg must be a type." for p in parameters: if isinstance(p, _Union) and p.__origin__ is Union: params.extend(p.__args__) else: - params.append(_type_check(p, msg)) + params.append(p) # Weed out strict duplicates, preserving the first of each occurrence. params = remove_dups(params) # Weed out subclasses. @@ -643,27 +644,19 @@ def _get_type_vars(self, tvars): def __repr__(self): if self.__origin__ is None: return super().__repr__() - return self._subs_repr([], []) - - def _subs_repr(self, tvars, args): - # This is an adapted equivalent of code in GenericMeta. - # Look there for detailed explanations. - if not self.__args__: - return super().__repr__() - current = self.__origin__ - orig_chain = [] - while current.__origin__ is not None: - orig_chain.append(current) - current = current.__origin__ - str_args = [] - for arg in self.__args__: - str_args.append(_replace_arg(arg, tvars, args)) - for cls in orig_chain: - new_str_args = [] - for i, arg in enumerate(cls.__args__): - new_str_args.append(_replace_arg(arg, cls.__parameters__, str_args)) - str_args = new_str_args - return super().__repr__() + '[%s]' % ', '.join(str_args) + tree = self._subs_tree([], []) + if not isinstance(tree, tuple): + return repr(tree) + return self._tree_repr(tree) + + def _tree_repr(self, tree): + arg_list = [] + for arg in tree[1:]: + if not isinstance(arg, tuple): + arg_list.append(_type_repr(arg)) + else: + arg_list.append(arg[0]._tree_repr(arg)) + return super().__repr__() + '[%s]' % ', '.join(arg_list) @_tp_cache def __getitem__(self, parameters): @@ -671,6 +664,11 @@ def __getitem__(self, parameters): raise TypeError("Cannot take a Union of no types.") if not isinstance(parameters, tuple): parameters = (parameters,) + if self.__origin__ is None: + msg = "Union[arg, ...]: each arg must be a type." + else: + msg = "Parameters to generic types must be types." + parameters = tuple(_type_check(p, msg) for p in parameters) if self is not Union: if not self.__parameters__: raise TypeError("%s is not a generic class" % repr(self)) @@ -948,40 +946,24 @@ def _eval_type(self, globalns, localns): def __repr__(self): if self.__origin__ is None: return super().__repr__() - return self._subs_repr([], []) - - def _subs_repr(self, tvars, args, *, for_callable=False): - assert len(tvars) == len(args) - # Construct the chain of __origin__'s. - current = self.__origin__ - orig_chain = [] - while current.__origin__ is not None: - orig_chain.append(current) - current = current.__origin__ - # Replace type variables in __args__ if asked ... - str_args = [] - for arg in self.__args__: - str_args.append(_replace_arg(arg, tvars, args)) - # ... then continue replacing down the origin chain. - for cls in orig_chain: - new_str_args = [] - for i, arg in enumerate(cls.__args__): - new_str_args.append(_replace_arg(arg, cls.__parameters__, str_args)) - str_args = new_str_args - if not for_callable: - return super().__repr__() + '[%s]' % ', '.join(str_args) - # Special format fot Callable - if str_args[0] == '...': - return super().__repr__() + '[..., %s]' % str_args[1] - if str_args[0] == '()': - return super().__repr__() + '[[], %s]' % str_args[1] - else: - return (super().__repr__() + - '[[%s], %s]' % (', '.join(str_args[:-1]), str_args[-1])) + return self._tree_repr(self._subs_tree([], [])) + + def _tree_repr(self, tree): + arg_list = [] + for arg in tree[1:]: + if arg == (): + arg_list.append('()') + elif not isinstance(arg, tuple): + arg_list.append(_type_repr(arg)) + else: + arg_list.append(arg[0]._tree_repr(arg)) + return super().__repr__() + '[%s]' % ', '.join(arg_list) def _subs_tree(self, tvars, args): # Construct the chain of __origin__'s. current = self.__origin__ + if current is None: + return self orig_chain = [] while current.__origin__ is not None: orig_chain.append(current) @@ -1121,16 +1103,6 @@ class _TypingEmpty: class TupleMeta(GenericMeta): """Metaclass for Tuple""" - def __repr__(self): - if self.__args__ == ((),): - return repr(Tuple) + '[()]' - return super().__repr__() - - def _subs_repr(self, tvars, args): - if not self.__args__: - return super().__repr__() - return super()._subs_repr(tvars, args) - @_tp_cache def __getitem__(self, parameters): if self.__origin__ is not None or not _geqv(self, Tuple): @@ -1194,12 +1166,25 @@ class CallableMeta(GenericMeta): def __repr__(self): if self.__origin__ is None: return super().__repr__() - return super()._subs_repr([], [], for_callable=True) - - def _subs_repr(self, tvars, args): - if not self.__args__: - return super().__repr__() - return super()._subs_repr(tvars, args) + return self._tree_repr(self._subs_tree([], [])) + + def _tree_repr(self, tree): + arg_list = [] + for arg in tree[1:]: + if not isinstance(arg, tuple) or len(arg) == 0: + if arg == (): + r = '[]' + elif arg == Ellipsis: + r = '...' + else: + r = _type_repr(arg) + arg_list.append(r) + else: + arg_list.append(arg[0]._tree_repr(arg)) + if len(arg_list) == 2: + return repr(tree[0]) + '[%s]' % ', '.join(arg_list) + return (repr(tree[0]) + + '[[%s], %s]' % (', '.join(arg_list[:-1]), arg_list[-1])) def __getitem__(self, parameters): if self.__origin__ is not None or not _geqv(self, Callable): From 65de9072034976cf639c2215a4f9671a814f2c1c Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 23 Oct 2016 21:11:56 +0200 Subject: [PATCH 08/39] Remove redundant code --- src/typing.py | 41 ++++++++++++++++------------------------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/src/typing.py b/src/typing.py index e0f15c31..06fc4a29 100644 --- a/src/typing.py +++ b/src/typing.py @@ -644,7 +644,7 @@ def _get_type_vars(self, tvars): def __repr__(self): if self.__origin__ is None: return super().__repr__() - tree = self._subs_tree([], []) + tree = self._subs_tree() if not isinstance(tree, tuple): return repr(tree) return self._tree_repr(tree) @@ -680,7 +680,7 @@ def __getitem__(self, parameters): ("many" if alen > elen else "few", repr(self), alen, elen)) return self.__class__(parameters, origin=self, _root=True) - def _subs_tree(self, tvars, args): + def _subs_tree(self, tvars=None, args=None): # This is an adapted equivalent of code in GenericMeta (removing duplicates). current = self.__origin__ orig_chain = [] @@ -689,12 +689,12 @@ def _subs_tree(self, tvars, args): current = current.__origin__ tree_args = [] for arg in self.__args__: - tree_args.append(_replace_arg_nos(arg, tvars, args)) + tree_args.append(_replace_arg(arg, tvars, args)) tree_args = remove_dups(tree_args) for cls in orig_chain: new_tree_args = [] for i, arg in enumerate(cls.__args__): - new_tree_args.append(_replace_arg_nos(arg, cls.__parameters__, + new_tree_args.append(_replace_arg(arg, cls.__parameters__, tree_args)) tree_args = remove_dups(new_tree_args) res = ((orig_chain[-1].__origin__ if orig_chain @@ -706,16 +706,15 @@ def _subs_tree(self, tvars, args): def __eq__(self, other): if isinstance(other, _Union): if self.__origin__ and other.__origin__: - return (frozenset(self._subs_tree([], [])) - == frozenset(other._subs_tree([], []))) + return frozenset(self._subs_tree()) == frozenset(other._subs_tree()) return self is other if self.__origin__: - return self._subs_tree([], []) is other + return self._subs_tree() is other return NotImplemented def __hash__(self): return hash((type(self).__name__, - frozenset(self._subs_tree([], [])) if self.__origin__ else ())) + frozenset(self._subs_tree()) if self.__origin__ else ())) def __instancecheck__(self, obj): raise TypeError("Unions cannot be used with isinstance().") @@ -768,16 +767,8 @@ def _geqv(a, b): def _replace_arg(arg, tvars, args): - if hasattr(arg, '_subs_repr'): - return arg._subs_repr(tvars, args) - if isinstance(arg, TypeVar): - for i, tvar in enumerate(tvars): - if arg == tvar: - return args[i] - return _type_repr(arg) - - -def _replace_arg_nos(arg, tvars, args): + if tvars is None: + return arg if hasattr(arg, '_subs_tree'): return arg._subs_tree(tvars, args) if isinstance(arg, TypeVar): @@ -946,7 +937,7 @@ def _eval_type(self, globalns, localns): def __repr__(self): if self.__origin__ is None: return super().__repr__() - return self._tree_repr(self._subs_tree([], [])) + return self._tree_repr(self._subs_tree()) def _tree_repr(self, tree): arg_list = [] @@ -959,7 +950,7 @@ def _tree_repr(self, tree): arg_list.append(arg[0]._tree_repr(arg)) return super().__repr__() + '[%s]' % ', '.join(arg_list) - def _subs_tree(self, tvars, args): + def _subs_tree(self, tvars=None, args=None): # Construct the chain of __origin__'s. current = self.__origin__ if current is None: @@ -971,12 +962,12 @@ def _subs_tree(self, tvars, args): # Replace type variables in __args__ if asked ... tree_args = () for arg in self.__args__: - tree_args += (_replace_arg_nos(arg, tvars, args),) + tree_args += (_replace_arg(arg, tvars, args),) # ... then continue replacing down the origin chain. for cls in orig_chain: new_tree_args = () for i, arg in enumerate(cls.__args__): - new_tree_args += (_replace_arg_nos(arg, cls.__parameters__, tree_args),) + new_tree_args += (_replace_arg(arg, cls.__parameters__, tree_args),) tree_args = new_tree_args return ((orig_chain[-1].__origin__ if orig_chain else self.__origin__),) + tree_args @@ -984,12 +975,12 @@ def __eq__(self, other): if not isinstance(other, GenericMeta): return NotImplemented if self.__origin__ is not None and other.__origin__ is not None: - return self._subs_tree([], []) == other._subs_tree([], []) + return self._subs_tree() == other._subs_tree() else: return self is other def __hash__(self): - return hash((self.__name__, self._subs_tree([], []) if self.__origin__ else ())) + return hash((self.__name__, self._subs_tree() if self.__origin__ else ())) @_tp_cache def __getitem__(self, params): @@ -1166,7 +1157,7 @@ class CallableMeta(GenericMeta): def __repr__(self): if self.__origin__ is None: return super().__repr__() - return self._tree_repr(self._subs_tree([], [])) + return self._tree_repr(self._subs_tree()) def _tree_repr(self, tree): arg_list = [] From 897de4e7e0d468478aa272d0147143c5b5f0b0ac Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 23 Oct 2016 21:42:54 +0200 Subject: [PATCH 09/39] Fix minor bugs + mini-refactoring --- src/typing.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/typing.py b/src/typing.py index 06fc4a29..e4806bed 100644 --- a/src/typing.py +++ b/src/typing.py @@ -506,8 +506,18 @@ def __subclasscheck__(self, cls): # (This one *is* for export!) AnyStr = TypeVar('AnyStr', bytes, str) +def flat_union(parameters): + # Flatten out Union[Union[...], ...] and type-check non-Union args. + params = [] + for p in parameters: + if isinstance(p, _Union) and p.__origin__ is Union: + params.extend(p.__args__) + else: + params.append(p) + return params def remove_dups(params): + # Weed out strict duplicates, preserving the first of each occurrence. all_params = set(params) if len(all_params) < len(params): new_params = [] @@ -596,14 +606,7 @@ def __new__(cls, parameters=None, origin=None, *args, _root=False): return self if not isinstance(parameters, tuple): raise TypeError("Expected parameters=") - # Flatten out Union[Union[...], ...] and type-check non-Union args. - params = [] - for p in parameters: - if isinstance(p, _Union) and p.__origin__ is Union: - params.extend(p.__args__) - else: - params.append(p) - # Weed out strict duplicates, preserving the first of each occurrence. + params = flat_union(parameters) params = remove_dups(params) # Weed out subclasses. # E.g. Union[int, Employee, Manager] == Union[int, Employee]. @@ -647,6 +650,8 @@ def __repr__(self): tree = self._subs_tree() if not isinstance(tree, tuple): return repr(tree) + if tree[0] is not Union: + return tree[0]._tree_repr(tree) return self._tree_repr(tree) def _tree_repr(self, tree): @@ -690,13 +695,13 @@ def _subs_tree(self, tvars=None, args=None): tree_args = [] for arg in self.__args__: tree_args.append(_replace_arg(arg, tvars, args)) - tree_args = remove_dups(tree_args) for cls in orig_chain: new_tree_args = [] for i, arg in enumerate(cls.__args__): new_tree_args.append(_replace_arg(arg, cls.__parameters__, - tree_args)) - tree_args = remove_dups(new_tree_args) + tree_args)) + tree_args = new_tree_args + tree_args = remove_dups(flat_union(tree_args)) res = ((orig_chain[-1].__origin__ if orig_chain else self.__origin__),) + tuple(tree_args) if len(res) == 2: From c687ffab2dc6bacf0ea949d30074d7fb2b685382 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 23 Oct 2016 22:02:19 +0200 Subject: [PATCH 10/39] Fix repr of Callable subclasses --- src/typing.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/typing.py b/src/typing.py index e4806bed..efc95019 100644 --- a/src/typing.py +++ b/src/typing.py @@ -1165,6 +1165,8 @@ def __repr__(self): return self._tree_repr(self._subs_tree()) def _tree_repr(self, tree): + if _gorg(self) is not Callable: + return super()._tree_repr(tree) arg_list = [] for arg in tree[1:]: if not isinstance(arg, tuple) or len(arg) == 0: From 582a37f541a7251f390ed71ec3f00953c6035328 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 23 Oct 2016 22:13:30 +0200 Subject: [PATCH 11/39] Don't be too pedantic in GenericMeta._eval_type --- src/typing.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/typing.py b/src/typing.py index efc95019..acc72bcf 100644 --- a/src/typing.py +++ b/src/typing.py @@ -926,10 +926,6 @@ def _eval_type(self, globalns, localns): in self.__args__) if self.__args__ else None if ev_origin == self.__origin__ and ev_args == self.__args__: return self - if ev_origin == self.__origin__: - ev_origin = self.__origin__ - if ev_args == self.__args__: - ev_args = self.__args__ return self.__class__(self.__name__, self.__bases__, dict(self.__dict__), From 02e6b14f522f933e18b5948976d07e627d73fb05 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 23 Oct 2016 22:57:40 +0200 Subject: [PATCH 12/39] Simplify __hash__ and factor out orig_chain --- src/typing.py | 64 +++++++++++++++++++++++++-------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/src/typing.py b/src/typing.py index acc72bcf..f0debe11 100644 --- a/src/typing.py +++ b/src/typing.py @@ -530,6 +530,16 @@ def remove_dups(params): return params +def _orig_chain(cls): + # Make of chain of origins (i.e. cls -> cls.__origin__) + current = cls.__origin__ + orig_chain = [] + while current.__origin__ is not None: + orig_chain.append(current) + current = current.__origin__ + return orig_chain + + def _tp_cache(func): cached = functools.lru_cache()(func) @functools.wraps(func) @@ -686,12 +696,11 @@ def __getitem__(self, parameters): return self.__class__(parameters, origin=self, _root=True) def _subs_tree(self, tvars=None, args=None): - # This is an adapted equivalent of code in GenericMeta (removing duplicates). - current = self.__origin__ - orig_chain = [] - while current.__origin__ is not None: - orig_chain.append(current) - current = current.__origin__ + # This is an adapted equivalent of code in GenericMeta, + # but removing duplicate args and flattens Union's. + if self is Union: + return (Union,) + orig_chain = _orig_chain(self) tree_args = [] for arg in self.__args__: tree_args.append(_replace_arg(arg, tvars, args)) @@ -701,25 +710,20 @@ def _subs_tree(self, tvars=None, args=None): new_tree_args.append(_replace_arg(arg, cls.__parameters__, tree_args)) tree_args = new_tree_args - tree_args = remove_dups(flat_union(tree_args)) - res = ((orig_chain[-1].__origin__ if orig_chain - else self.__origin__),) + tuple(tree_args) - if len(res) == 2: - return res[1] - return res + tree_args = tuple(remove_dups(flat_union(tree_args))) + if len(tree_args) == 1: + return tree_args[0] # Union of a single type is that type + return (Union,) + tree_args def __eq__(self, other): if isinstance(other, _Union): - if self.__origin__ and other.__origin__: - return frozenset(self._subs_tree()) == frozenset(other._subs_tree()) - return self is other - if self.__origin__: - return self._subs_tree() is other - return NotImplemented + return frozenset(self._subs_tree()) == frozenset(other._subs_tree()) + return self._subs_tree() == other def __hash__(self): - return hash((type(self).__name__, - frozenset(self._subs_tree()) if self.__origin__ else ())) + if self is Union: + return hash(frozenset((_Union,))) + return hash(frozenset(self._subs_tree())) def __instancecheck__(self, obj): raise TypeError("Unions cannot be used with isinstance().") @@ -952,14 +956,9 @@ def _tree_repr(self, tree): return super().__repr__() + '[%s]' % ', '.join(arg_list) def _subs_tree(self, tvars=None, args=None): - # Construct the chain of __origin__'s. - current = self.__origin__ - if current is None: + if self.__origin__ is None: return self - orig_chain = [] - while current.__origin__ is not None: - orig_chain.append(current) - current = current.__origin__ + orig_chain = _orig_chain(self) # Replace type variables in __args__ if asked ... tree_args = () for arg in self.__args__: @@ -970,18 +969,19 @@ def _subs_tree(self, tvars=None, args=None): for i, arg in enumerate(cls.__args__): new_tree_args += (_replace_arg(arg, cls.__parameters__, tree_args),) tree_args = new_tree_args - return ((orig_chain[-1].__origin__ if orig_chain else self.__origin__),) + tree_args + return (_gorg(self),) + tree_args def __eq__(self, other): if not isinstance(other, GenericMeta): return NotImplemented - if self.__origin__ is not None and other.__origin__ is not None: - return self._subs_tree() == other._subs_tree() - else: + if self.__origin__ is None or other.__origin__ is None: return self is other + return self._subs_tree() == other._subs_tree() def __hash__(self): - return hash((self.__name__, self._subs_tree() if self.__origin__ else ())) + if self.__origin__ is None: + return hash((self.__name__,)) + return hash(self._subs_tree()) @_tp_cache def __getitem__(self, params): From 8d802cc325f05e1707fc0d6cc3556bdaaf26d860 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 23 Oct 2016 23:10:38 +0200 Subject: [PATCH 13/39] Remove outdated tests + be more pedanctic in _Union._eval_type --- src/test_typing.py | 21 +-------------------- src/typing.py | 12 ++++++------ 2 files changed, 7 insertions(+), 26 deletions(-) diff --git a/src/test_typing.py b/src/test_typing.py index 0d8532eb..fb1a7b45 100644 --- a/src/test_typing.py +++ b/src/test_typing.py @@ -142,8 +142,7 @@ def test_union_unique(self): self.assertEqual(Union[X, X], X) self.assertNotEqual(Union[X, int], Union[X]) self.assertNotEqual(Union[X, int], Union[int]) - self.assertEqual(Union[X, int].__union_params__, (X, int)) - self.assertEqual(Union[X, int].__union_set_params__, {X, int}) + self.assertEqual(Union[X, int].__args__, (X, int)) def test_union_constrained(self): A = TypeVar('A', str, bytes) @@ -311,8 +310,6 @@ def Elem(*args): class TupleTests(BaseTestCase): def test_basics(self): - with self.assertRaises(TypeError): - issubclass(Tuple[int, str], Tuple) with self.assertRaises(TypeError): issubclass(Tuple, Tuple[int, str]) with self.assertRaises(TypeError): @@ -367,22 +364,6 @@ def test_eq_hash(self): self.assertNotEqual(Callable[[int], int], Callable[[], int]) self.assertNotEqual(Callable[[int], int], Callable) - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - - class C(Callable): - pass - - with self.assertRaises(TypeError): - - class C(type(Callable)): - pass - - with self.assertRaises(TypeError): - - class C(Callable[[int], int]): - pass - def test_cannot_instantiate(self): with self.assertRaises(TypeError): Callable() diff --git a/src/typing.py b/src/typing.py index f0debe11..f77e616d 100644 --- a/src/typing.py +++ b/src/typing.py @@ -643,12 +643,12 @@ def __new__(cls, parameters=None, origin=None, *args, _root=False): def _eval_type(self, globalns, localns): if self.__args__ is None: return self - p = tuple(_eval_type(t, globalns, localns) - for t in self.__args__) - return self.__class__(p, - _eval_type(self.__origin__, globalns, localns) - if self.__origin__ is not None else None, - _root=True) + ev_args = tuple(_eval_type(t, globalns, localns) + for t in self.__args__) + ev_origin = _eval_type(self.__origin__, globalns, localns) + if ev_args == self.__args__ and ev_origin == self.__origin__: + return self + return self.__class__(ev_args, ev_origin, _root=True) def _get_type_vars(self, tvars): if self.__origin__ and self.__parameters__: From 4e2527c000759468d960d8cbb741d9e41b8cc0eb Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 23 Oct 2016 23:51:50 +0200 Subject: [PATCH 14/39] Add tests --- src/test_typing.py | 46 ++++++++++++++++++++++++++++++++++++++++++++++ src/typing.py | 4 +--- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/src/test_typing.py b/src/test_typing.py index fb1a7b45..a99c04ec 100644 --- a/src/test_typing.py +++ b/src/test_typing.py @@ -691,6 +691,52 @@ class D(C, List[T][U][V]): ... self.assertEqual(C.__orig_bases__, (List[T][U][V],)) self.assertEqual(D.__orig_bases__, (C, List[T][U][V])) + def test_extended_generic_rules_eq(self): + self.assertEqual(Tuple[T, T][int], Tuple[int, int]) + self.assertEqual(Union[T, int][int], int) + self.assertEqual(Callable[[T], T][KT], Callable[[KT], KT]) + self.assertEqual(Callable[..., List[T]][int], Callable[..., List[int]]) + + def test_extended_generic_rules_repr(self): + self.assertEqual(repr(Union[Tuple, Callable]).replace('typing.', ''), + 'Union[Tuple, Callable]') + self.assertEqual(repr(Union[Tuple, Tuple[int]]).replace('typing.', ''), + 'Tuple') + self.assertEqual(repr(Callable[..., Optional[T]][int]).replace('typing.', ''), + 'Callable[..., Union[int, NoneType]]') + + def test_extended_generic_rules_subclassing(self): + class T1(Tuple[T, KT]): ... + class T2(Tuple[T, ...]): ... + class C1(Callable[[T], T]): ... + class C2(Callable[..., int]): + def __call__(self): + return None + + self.assertEqual(T1.__parameters__, (T, KT)) + self.assertEqual(T1[int, str].__args__, (int, str)) + self.assertEqual(T1[int, T].__origin__, T1) + + self.assertEqual(T2.__parameters__, (T,)) + with self.assertRaises(TypeError): + T1[int] + with self.assertRaises(TypeError): + T2[int, str] + + self.assertEqual(repr(C1[int]).split('.')[-1], 'C1[int]') + self.assertEqual(C2.__parameters__, ()) + self.assertIsInstance(C2(), collections_abc.Callable) + self.assertIsSubclass(C2, collections_abc.Callable) + self.assertIsSubclass(C1, collections_abc.Callable) + self.assertIsInstance(T1(), tuple) + + def test_fail_with_bare_union(self): + with self.assertRaises(TypeError): + List[Union] + with self.assertRaises(TypeError): + Tuple[Optional] + with self.assertRaises(TypeError): + ClassVar[ClassVar] def test_pickle(self): global C # pickle wants to reference the class by name diff --git a/src/typing.py b/src/typing.py index f77e616d..1cbb62df 100644 --- a/src/typing.py +++ b/src/typing.py @@ -614,8 +614,6 @@ def __new__(cls, parameters=None, origin=None, *args, _root=False): self.__args__ = parameters self.__origin__ = origin return self - if not isinstance(parameters, tuple): - raise TypeError("Expected parameters=") params = flat_union(parameters) params = remove_dups(params) # Weed out subclasses. @@ -777,7 +775,7 @@ def _geqv(a, b): def _replace_arg(arg, tvars, args): if tvars is None: - return arg + tvars = [] if hasattr(arg, '_subs_tree'): return arg._subs_tree(tvars, args) if isinstance(arg, TypeVar): From 92da67fd6a9313e5cfbe70e9b0980d1bee527751 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 24 Oct 2016 09:25:34 +0200 Subject: [PATCH 15/39] Factor out _subs_tree for Union and Generic --- src/typing.py | 71 +++++++++++++++++++++++---------------------------- 1 file changed, 32 insertions(+), 39 deletions(-) diff --git a/src/typing.py b/src/typing.py index 1cbb62df..eae3f1ee 100644 --- a/src/typing.py +++ b/src/typing.py @@ -506,6 +506,7 @@ def __subclasscheck__(self, cls): # (This one *is* for export!) AnyStr = TypeVar('AnyStr', bytes, str) + def flat_union(parameters): # Flatten out Union[Union[...], ...] and type-check non-Union args. params = [] @@ -516,6 +517,7 @@ def flat_union(parameters): params.append(p) return params + def remove_dups(params): # Weed out strict duplicates, preserving the first of each occurrence. all_params = set(params) @@ -530,16 +532,6 @@ def remove_dups(params): return params -def _orig_chain(cls): - # Make of chain of origins (i.e. cls -> cls.__origin__) - current = cls.__origin__ - orig_chain = [] - while current.__origin__ is not None: - orig_chain.append(current) - current = current.__origin__ - return orig_chain - - def _tp_cache(func): cached = functools.lru_cache()(func) @functools.wraps(func) @@ -694,29 +686,18 @@ def __getitem__(self, parameters): return self.__class__(parameters, origin=self, _root=True) def _subs_tree(self, tvars=None, args=None): - # This is an adapted equivalent of code in GenericMeta, - # but removing duplicate args and flattens Union's. - if self is Union: + tree_args = _subs_tree(self, tvars, args) + if tree_args is Union: return (Union,) - orig_chain = _orig_chain(self) - tree_args = [] - for arg in self.__args__: - tree_args.append(_replace_arg(arg, tvars, args)) - for cls in orig_chain: - new_tree_args = [] - for i, arg in enumerate(cls.__args__): - new_tree_args.append(_replace_arg(arg, cls.__parameters__, - tree_args)) - tree_args = new_tree_args tree_args = tuple(remove_dups(flat_union(tree_args))) if len(tree_args) == 1: return tree_args[0] # Union of a single type is that type return (Union,) + tree_args def __eq__(self, other): - if isinstance(other, _Union): - return frozenset(self._subs_tree()) == frozenset(other._subs_tree()) - return self._subs_tree() == other + if not isinstance(other, _Union): + return self._subs_tree() == other + return frozenset(self._subs_tree()) == frozenset(other._subs_tree()) def __hash__(self): if self is Union: @@ -785,6 +766,28 @@ def _replace_arg(arg, tvars, args): return arg +def _subs_tree(cls, tvars, args): + if cls.__origin__ is None: + return cls + # Make of chain of origins (i.e. cls -> cls.__origin__) + current = cls.__origin__ + orig_chain = [] + while current.__origin__ is not None: + orig_chain.append(current) + current = current.__origin__ + # Replace type variables in __args__ if asked ... + tree_args = [] + for arg in cls.__args__: + tree_args.append(_replace_arg(arg, tvars, args)) + # ... then continue replacing down the origin chain. + for ocls in orig_chain: + new_tree_args = [] + for i, arg in enumerate(ocls.__args__): + new_tree_args.append(_replace_arg(arg, ocls.__parameters__, tree_args)) + tree_args = new_tree_args + return tree_args + + def _next_in_mro(cls): """Helper for Generic.__new__. @@ -954,20 +957,10 @@ def _tree_repr(self, tree): return super().__repr__() + '[%s]' % ', '.join(arg_list) def _subs_tree(self, tvars=None, args=None): - if self.__origin__ is None: + tree_args = _subs_tree(self, tvars, args) + if tree_args is self: return self - orig_chain = _orig_chain(self) - # Replace type variables in __args__ if asked ... - tree_args = () - for arg in self.__args__: - tree_args += (_replace_arg(arg, tvars, args),) - # ... then continue replacing down the origin chain. - for cls in orig_chain: - new_tree_args = () - for i, arg in enumerate(cls.__args__): - new_tree_args += (_replace_arg(arg, cls.__parameters__, tree_args),) - tree_args = new_tree_args - return (_gorg(self),) + tree_args + return (_gorg(self),) + tuple(tree_args) def __eq__(self, other): if not isinstance(other, GenericMeta): From 7563c92e19c01cde51889a20e65176936d85234e Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 24 Oct 2016 09:31:24 +0200 Subject: [PATCH 16/39] Prohibit plain Generic[T] as type argument --- src/typing.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/typing.py b/src/typing.py index eae3f1ee..501c2684 100644 --- a/src/typing.py +++ b/src/typing.py @@ -356,7 +356,8 @@ def _type_check(arg, msg): not isinstance(arg, (type, _TypingBase)) and not callable(arg)): raise TypeError(msg + " Got %.100r." % (arg,)) if (type(arg).__name__ in ('_Union', '_Optional') - and not getattr(arg, '__origin__', None)): + and not getattr(arg, '__origin__', None) + or isinstance(arg, TypingMeta) and _gorg(arg) in (Generic, _Protocol)): raise TypeError("Plain %s is not valid as type argument" % arg) return arg From f33545b2161471302a413cdd634502ed1075e5fe Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 24 Oct 2016 09:37:02 +0200 Subject: [PATCH 17/39] Correct a comment --- src/typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/typing.py b/src/typing.py index 501c2684..f7a84246 100644 --- a/src/typing.py +++ b/src/typing.py @@ -509,7 +509,7 @@ def __subclasscheck__(self, cls): def flat_union(parameters): - # Flatten out Union[Union[...], ...] and type-check non-Union args. + # Flatten out Union[Union[...], ...]. params = [] for p in parameters: if isinstance(p, _Union) and p.__origin__ is Union: From 4a996b2e53d6907edfde44bd40165450914e3017 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 24 Oct 2016 10:14:42 +0200 Subject: [PATCH 18/39] Erase but preserve type also for Callable --- src/typing.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/typing.py b/src/typing.py index f7a84246..4076cd88 100644 --- a/src/typing.py +++ b/src/typing.py @@ -1039,6 +1039,20 @@ def __instancecheck__(self, instance): Generic = None +def _generic_new(cls, *args, **kwds): + if cls.__origin__ is None: + return cls.__next_in_mro__.__new__(cls) + else: + origin = _gorg(cls) + obj = cls.__next_in_mro__.__new__(origin) + try: + obj.__orig_class__ = cls + except AttributeError: + pass + obj.__init__(*args, **kwds) + return obj + + class Generic(metaclass=GenericMeta): """Abstract base class for generic types. @@ -1063,17 +1077,7 @@ def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT: __slots__ = () def __new__(cls, *args, **kwds): - if cls.__origin__ is None: - return cls.__next_in_mro__.__new__(cls) - else: - origin = _gorg(cls) - obj = cls.__next_in_mro__.__new__(origin) - try: - obj.__orig_class__ = cls - except AttributeError: - pass - obj.__init__(*args, **kwds) - return obj + return _generic_new(cls, *args, **kwds) class _TypingEllipsis: @@ -1236,7 +1240,7 @@ def __new__(cls, *args, **kwds): if _geqv(cls, Callable): raise TypeError("Type Callable cannot be instantiated; " "use a non-abstract subclass instead") - return super().__new__(cls, *args, **kwds) + return _generic_new(cls, *args, **kwds) class _ClassVar(_FinalTypingBase, _root=True): From 6564742cba2e9eeeee946553df8bee893b09b9b2 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 24 Oct 2016 10:37:28 +0200 Subject: [PATCH 19/39] Erase but preserve type for all generics --- src/typing.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/typing.py b/src/typing.py index 4076cd88..5fb58cd5 100644 --- a/src/typing.py +++ b/src/typing.py @@ -796,10 +796,10 @@ def _next_in_mro(cls): Generic[...] in cls.__mro__. """ 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: - next_in_mro = cls.__mro__[i+1] + # Look for the first occurrence of non-generic class. + for c in cls.__mro__[:-1]: + if not isinstance(c, GenericMeta): + return c return next_in_mro @@ -1146,7 +1146,7 @@ def __new__(cls, *args, **kwds): if _geqv(cls, Tuple): raise TypeError("Type Tuple cannot be instantiated; " "use tuple() instead") - return super().__new__(cls, *args, **kwds) + return _generic_new(cls, *args, **kwds) class CallableMeta(GenericMeta): @@ -1789,7 +1789,7 @@ def __new__(cls, *args, **kwds): if _geqv(cls, List): raise TypeError("Type List cannot be instantiated; " "use list() instead") - return list.__new__(cls, *args, **kwds) + return _generic_new(cls, *args, **kwds) class Set(set, MutableSet[T], extra=set): @@ -1800,7 +1800,7 @@ def __new__(cls, *args, **kwds): if _geqv(cls, Set): raise TypeError("Type Set cannot be instantiated; " "use set() instead") - return set.__new__(cls, *args, **kwds) + return _generic_new(cls, *args, **kwds) class FrozenSet(frozenset, AbstractSet[T_co], extra=frozenset): @@ -1810,7 +1810,7 @@ def __new__(cls, *args, **kwds): if _geqv(cls, FrozenSet): raise TypeError("Type FrozenSet cannot be instantiated; " "use frozenset() instead") - return frozenset.__new__(cls, *args, **kwds) + return _generic_new(cls, *args, **kwds) class MappingView(Sized, Iterable[T_co], extra=collections_abc.MappingView): @@ -1847,7 +1847,7 @@ def __new__(cls, *args, **kwds): if _geqv(cls, Dict): raise TypeError("Type Dict cannot be instantiated; " "use dict() instead") - return dict.__new__(cls, *args, **kwds) + return _generic_new(cls, *args, **kwds) class DefaultDict(collections.defaultdict, MutableMapping[KT, VT], extra=collections.defaultdict): @@ -1858,7 +1858,7 @@ def __new__(cls, *args, **kwds): if _geqv(cls, DefaultDict): raise TypeError("Type DefaultDict cannot be instantiated; " "use collections.defaultdict() instead") - return collections.defaultdict.__new__(cls, *args, **kwds) + return _generic_new(cls, *args, **kwds) # Determine what base class to use for Generator. if hasattr(collections_abc, 'Generator'): @@ -1877,7 +1877,7 @@ def __new__(cls, *args, **kwds): if _geqv(cls, Generator): raise TypeError("Type Generator cannot be instantiated; " "create a subclass instead") - return super().__new__(cls, *args, **kwds) + return _generic_new(cls, *args, **kwds) # Internal type variable used for Type[]. From 184f0899d1e758d400e49cbfc1bb0510eb22361f Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 24 Oct 2016 19:54:37 +0200 Subject: [PATCH 20/39] Restore original __next_in_mro__ and use more precise bases --- src/typing.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/typing.py b/src/typing.py index 5fb58cd5..58e7675f 100644 --- a/src/typing.py +++ b/src/typing.py @@ -796,10 +796,10 @@ def _next_in_mro(cls): Generic[...] in cls.__mro__. """ next_in_mro = object - # Look for the first occurrence of non-generic class. - for c in cls.__mro__[:-1]: - if not isinstance(c, GenericMeta): - return c + # 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: + next_in_mro = cls.__mro__[i+1] return next_in_mro @@ -1039,12 +1039,12 @@ def __instancecheck__(self, instance): Generic = None -def _generic_new(cls, *args, **kwds): +def _generic_new(base_cls, cls, *args, **kwds): if cls.__origin__ is None: - return cls.__next_in_mro__.__new__(cls) + return base_cls.__new__(cls) else: origin = _gorg(cls) - obj = cls.__next_in_mro__.__new__(origin) + obj = base_cls.__new__(origin) try: obj.__orig_class__ = cls except AttributeError: @@ -1077,7 +1077,7 @@ def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT: __slots__ = () def __new__(cls, *args, **kwds): - return _generic_new(cls, *args, **kwds) + return _generic_new(cls.__next_in_mro__, cls, *args, **kwds) class _TypingEllipsis: @@ -1146,7 +1146,7 @@ def __new__(cls, *args, **kwds): if _geqv(cls, Tuple): raise TypeError("Type Tuple cannot be instantiated; " "use tuple() instead") - return _generic_new(cls, *args, **kwds) + return _generic_new(tuple, cls, *args, **kwds) class CallableMeta(GenericMeta): @@ -1240,7 +1240,7 @@ def __new__(cls, *args, **kwds): if _geqv(cls, Callable): raise TypeError("Type Callable cannot be instantiated; " "use a non-abstract subclass instead") - return _generic_new(cls, *args, **kwds) + return _generic_new(cls.__next_in_mro__, cls, *args, **kwds) class _ClassVar(_FinalTypingBase, _root=True): @@ -1789,7 +1789,7 @@ def __new__(cls, *args, **kwds): if _geqv(cls, List): raise TypeError("Type List cannot be instantiated; " "use list() instead") - return _generic_new(cls, *args, **kwds) + return _generic_new(list, cls, *args, **kwds) class Set(set, MutableSet[T], extra=set): @@ -1800,7 +1800,7 @@ def __new__(cls, *args, **kwds): if _geqv(cls, Set): raise TypeError("Type Set cannot be instantiated; " "use set() instead") - return _generic_new(cls, *args, **kwds) + return _generic_new(set, cls, *args, **kwds) class FrozenSet(frozenset, AbstractSet[T_co], extra=frozenset): @@ -1810,7 +1810,7 @@ def __new__(cls, *args, **kwds): if _geqv(cls, FrozenSet): raise TypeError("Type FrozenSet cannot be instantiated; " "use frozenset() instead") - return _generic_new(cls, *args, **kwds) + return _generic_new(frozenset, cls, *args, **kwds) class MappingView(Sized, Iterable[T_co], extra=collections_abc.MappingView): @@ -1847,7 +1847,7 @@ def __new__(cls, *args, **kwds): if _geqv(cls, Dict): raise TypeError("Type Dict cannot be instantiated; " "use dict() instead") - return _generic_new(cls, *args, **kwds) + return _generic_new(dict, cls, *args, **kwds) class DefaultDict(collections.defaultdict, MutableMapping[KT, VT], extra=collections.defaultdict): @@ -1858,7 +1858,7 @@ def __new__(cls, *args, **kwds): if _geqv(cls, DefaultDict): raise TypeError("Type DefaultDict cannot be instantiated; " "use collections.defaultdict() instead") - return _generic_new(cls, *args, **kwds) + return _generic_new(collections.defaultdict, cls, *args, **kwds) # Determine what base class to use for Generator. if hasattr(collections_abc, 'Generator'): @@ -1877,7 +1877,7 @@ def __new__(cls, *args, **kwds): if _geqv(cls, Generator): raise TypeError("Type Generator cannot be instantiated; " "create a subclass instead") - return _generic_new(cls, *args, **kwds) + return _generic_new(_G_base, cls, *args, **kwds) # Internal type variable used for Type[]. From b613325c09e1c24d7f5e06a78638d1d4489b31bd Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 24 Oct 2016 20:42:26 +0200 Subject: [PATCH 21/39] Preserve __qualname__ on subscription --- src/typing.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/typing.py b/src/typing.py index 58e7675f..054c8856 100644 --- a/src/typing.py +++ b/src/typing.py @@ -919,6 +919,9 @@ def __new__(cls, name, bases, namespace, self.__subclasshook__ = _make_subclasshook(self) if isinstance(extra, abc.ABCMeta): self._abc_registry = extra._abc_registry + + if origin: + self.__qualname__ = origin.__qualname__ return self def _get_type_vars(self, tvars): From 011b9d86c7eb65a41a9f2c7a67df3fad25722fef Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 25 Oct 2016 00:58:19 +0200 Subject: [PATCH 22/39] Add tests and factor common stuff for Union --- src/test_typing.py | 22 +++++++++++++ src/typing.py | 78 +++++++++++++++++++++------------------------- 2 files changed, 58 insertions(+), 42 deletions(-) diff --git a/src/test_typing.py b/src/test_typing.py index a99c04ec..acc2db6d 100644 --- a/src/test_typing.py +++ b/src/test_typing.py @@ -143,6 +143,8 @@ def test_union_unique(self): self.assertNotEqual(Union[X, int], Union[X]) self.assertNotEqual(Union[X, int], Union[int]) self.assertEqual(Union[X, int].__args__, (X, int)) + self.assertEqual(Union[X, int].__parameters__, (X,)) + self.assertIs(Union[X, int].__origin__, Union) def test_union_constrained(self): A = TypeVar('A', str, bytes) @@ -692,18 +694,27 @@ class D(C, List[T][U][V]): ... self.assertEqual(D.__orig_bases__, (C, List[T][U][V])) def test_extended_generic_rules_eq(self): + T = TypeVar('T') + U = TypeVar('U') self.assertEqual(Tuple[T, T][int], Tuple[int, int]) self.assertEqual(Union[T, int][int], int) + self.assertEqual(Union[T, U][int, Union[int, str]], Union[int, str]) + class Base: ... + class Derived(Base): ... + self.assertEqual(Union[T, Base][Derived], Base) self.assertEqual(Callable[[T], T][KT], Callable[[KT], KT]) self.assertEqual(Callable[..., List[T]][int], Callable[..., List[int]]) def test_extended_generic_rules_repr(self): + T = TypeVar('T') self.assertEqual(repr(Union[Tuple, Callable]).replace('typing.', ''), 'Union[Tuple, Callable]') self.assertEqual(repr(Union[Tuple, Tuple[int]]).replace('typing.', ''), 'Tuple') self.assertEqual(repr(Callable[..., Optional[T]][int]).replace('typing.', ''), 'Callable[..., Union[int, NoneType]]') + self.assertEqual(repr(Callable[[], List[T]][int]).replace('typing.', ''), + 'Callable[[], List[int]]') def test_extended_generic_rules_subclassing(self): class T1(Tuple[T, KT]): ... @@ -737,6 +748,17 @@ def test_fail_with_bare_union(self): Tuple[Optional] with self.assertRaises(TypeError): ClassVar[ClassVar] + with self.assertRaises(TypeError): + List[ClassVar[int]] + + def test_fail_with_bare_generic(self): + T = TypeVar('T') + with self.assertRaises(TypeError): + List[Generic] + with self.assertRaises(TypeError): + Tuple[Generic[T]] + with self.assertRaises(TypeError): + List[typing._Protocol] def test_pickle(self): global C # pickle wants to reference the class by name diff --git a/src/typing.py b/src/typing.py index 054c8856..5e70c1a9 100644 --- a/src/typing.py +++ b/src/typing.py @@ -508,18 +508,16 @@ def __subclasscheck__(self, cls): AnyStr = TypeVar('AnyStr', bytes, str) -def flat_union(parameters): +def _remove_dups_flatten(parameters): # Flatten out Union[Union[...], ...]. params = [] for p in parameters: if isinstance(p, _Union) and p.__origin__ is Union: params.extend(p.__args__) + elif isinstance(p, tuple) and len(p) > 0 and p[0] is Union: + params.extend(p[1:]) else: params.append(p) - return params - - -def remove_dups(params): # Weed out strict duplicates, preserving the first of each occurrence. all_params = set(params) if len(all_params) < len(params): @@ -530,7 +528,32 @@ def remove_dups(params): all_params.remove(t) params = new_params assert not all_params, all_params - return params + # Weed out subclasses. + # E.g. Union[int, Employee, Manager] == Union[int, Employee]. + # If object is present it will be sole survivor among proper classes. + # Never discard type variables. + # (In particular, Union[str, AnyStr] != AnyStr.) + all_params = set(params) + for t1 in params: + if not isinstance(t1, type): + continue + if any(isinstance(t2, type) and issubclass(t1, t2) + for t2 in all_params - {t1} + if not (isinstance(t2, GenericMeta) and + t2.__origin__ is not None)): + all_params.remove(t1) + return tuple(t for t in params if t in all_params) + + +def _check_generic(cls, parameters): + # Check correct count for generic parameters for a cls + if not cls.__parameters__: + raise TypeError("%s is not a generic class" % repr(cls)) + alen = len(parameters) + elen = len(cls.__parameters__) + if alen != elen: + raise TypeError("Too %s parameters for %s; actual %s, expected %s" % + ("many" if alen > elen else "few", repr(cls), alen, elen)) def _tp_cache(func): @@ -607,27 +630,12 @@ def __new__(cls, parameters=None, origin=None, *args, _root=False): self.__args__ = parameters self.__origin__ = origin return self - params = flat_union(parameters) - params = remove_dups(params) - # Weed out subclasses. - # E.g. Union[int, Employee, Manager] == Union[int, Employee]. - # If object is present it will be sole survivor among proper classes. - # Never discard type variables. - # (In particular, Union[str, AnyStr] != AnyStr.) - all_params = set(params) - for t1 in params: - if not isinstance(t1, type): - continue - if any(isinstance(t2, type) and issubclass(t1, t2) - for t2 in all_params - {t1} - if not (isinstance(t2, GenericMeta) and - t2.__origin__ is not None)): - all_params.remove(t1) + params = _remove_dups_flatten(parameters) # It's not a union if there's only one type left. - if len(all_params) == 1: - return all_params.pop() + if len(params) == 1: + return params[0] self.__origin__ = origin - self.__args__ = tuple(t for t in params if t in all_params) + self.__args__ = params self.__parameters__ = _type_vars(self.__args__) return self @@ -676,21 +684,14 @@ def __getitem__(self, parameters): msg = "Parameters to generic types must be types." parameters = tuple(_type_check(p, msg) for p in parameters) if self is not Union: - if not self.__parameters__: - raise TypeError("%s is not a generic class" % repr(self)) - alen = len(parameters) - elen = len(self.__parameters__) - if alen != elen: - raise TypeError( - "Too %s parameters for %s; actual %s, expected %s" % - ("many" if alen > elen else "few", repr(self), alen, elen)) + _check_generic(self, parameters) return self.__class__(parameters, origin=self, _root=True) def _subs_tree(self, tvars=None, args=None): tree_args = _subs_tree(self, tvars, args) if tree_args is Union: return (Union,) - tree_args = tuple(remove_dups(flat_union(tree_args))) + tree_args = _remove_dups_flatten(tree_args) if len(tree_args) == 1: return tree_args[0] # Union of a single type is that type return (Union,) + tree_args @@ -1010,14 +1011,7 @@ def __getitem__(self, params): repr(self)) else: # Subscripting a regular Generic subclass. - if not self.__parameters__: - raise TypeError("%s is not a generic class" % repr(self)) - alen = len(params) - elen = len(self.__parameters__) - if alen != elen: - raise TypeError( - "Too %s parameters for %s; actual %s, expected %s" % - ("many" if alen > elen else "few", repr(self), alen, elen)) + _check_generic(self, params) tvars = _type_vars(params) args = params return self.__class__(self.__name__, From 52712a6c82f8b63a2f0ca1147c4ff397ccd61579 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 25 Oct 2016 01:30:15 +0200 Subject: [PATCH 23/39] Add even more tests --- src/test_typing.py | 23 ++++++++++++++++++++++- src/typing.py | 2 -- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/test_typing.py b/src/test_typing.py index acc2db6d..2fa17cf8 100644 --- a/src/test_typing.py +++ b/src/test_typing.py @@ -716,6 +716,11 @@ def test_extended_generic_rules_repr(self): self.assertEqual(repr(Callable[[], List[T]][int]).replace('typing.', ''), 'Callable[[], List[int]]') + def test_generic_forvard_ref(self): + def foobar(x: List[List['T']]): ... + T = TypeVar('T') + self.assertEqual(get_type_hints(foobar, globals(), locals()), {'x': List[List[T]]}) + def test_extended_generic_rules_subclassing(self): class T1(Tuple[T, KT]): ... class T2(Tuple[T, ...]): ... @@ -760,6 +765,22 @@ def test_fail_with_bare_generic(self): with self.assertRaises(TypeError): List[typing._Protocol] + def test_type_erasure_special(self): + T = TypeVar('T') + class MyTup(Tuple[T, T]): ... + self.assertIs(MyTup[int]().__class__, MyTup) + self.assertIs(MyTup[int]().__orig_class__, MyTup[int]) + class MyCall(Callable[..., T]): + def __call__(self): return None + self.assertIs(MyCall[T]().__class__, MyCall) + self.assertIs(MyCall[T]().__orig_class__, MyCall[T]) + class MyDict(typing.Dict[T, T]): ... + self.assertIs(MyDict[int]().__class__, MyDict) + self.assertIs(MyDict[int]().__orig_class__, MyDict[int]) + class MyDef(typing.DefaultDict[str, T]): ... + self.assertIs(MyDef[int]().__class__, MyDef) + self.assertIs(MyDef[int]().__orig_class__, MyDef[int]) + def test_pickle(self): global C # pickle wants to reference the class by name T = TypeVar('T') @@ -801,7 +822,7 @@ class C(Generic[T]): X = C[int] self.assertEqual(X.__module__, __name__) if not PY32: - self.assertEqual(X.__qualname__, 'C') + self.assertTrue(X.__qualname__.endswith('..C')) self.assertEqual(repr(X).split('.')[-1], 'C[int]') class Y(C[int]): diff --git a/src/typing.py b/src/typing.py index 5e70c1a9..3f2f5d8b 100644 --- a/src/typing.py +++ b/src/typing.py @@ -1161,8 +1161,6 @@ def _tree_repr(self, tree): if not isinstance(arg, tuple) or len(arg) == 0: if arg == (): r = '[]' - elif arg == Ellipsis: - r = '...' else: r = _type_repr(arg) arg_list.append(r) From 26411514cb0818e9220166557fb12076bd92bbb5 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 25 Oct 2016 01:42:54 +0200 Subject: [PATCH 24/39] One more test + minor code simplification --- src/test_typing.py | 2 ++ src/typing.py | 10 ++++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/test_typing.py b/src/test_typing.py index 2fa17cf8..582c6b07 100644 --- a/src/test_typing.py +++ b/src/test_typing.py @@ -720,6 +720,8 @@ def test_generic_forvard_ref(self): def foobar(x: List[List['T']]): ... T = TypeVar('T') self.assertEqual(get_type_hints(foobar, globals(), locals()), {'x': List[List[T]]}) + def barfoo(x: Tuple[T, ...]): ... + self.assertIs(get_type_hints(barfoo, globals(), locals())['x'], Tuple[T, ...]) def test_extended_generic_rules_subclassing(self): class T1(Tuple[T, KT]): ... diff --git a/src/typing.py b/src/typing.py index 3f2f5d8b..1fc43ddd 100644 --- a/src/typing.py +++ b/src/typing.py @@ -1158,12 +1158,10 @@ def _tree_repr(self, tree): return super()._tree_repr(tree) arg_list = [] for arg in tree[1:]: - if not isinstance(arg, tuple) or len(arg) == 0: - if arg == (): - r = '[]' - else: - r = _type_repr(arg) - arg_list.append(r) + if arg == (): + arg_list.append('[]') + elif not isinstance(arg, tuple): + arg_list.append(_type_repr(arg)) else: arg_list.append(arg[0]._tree_repr(arg)) if len(arg_list) == 2: From 9f1fb98df873cf8828dfdb6d8e3099e3f1176fbd Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 25 Oct 2016 02:10:48 +0200 Subject: [PATCH 25/39] Polishing code --- src/typing.py | 72 +++++++++++++++++++-------------------------------- 1 file changed, 27 insertions(+), 45 deletions(-) diff --git a/src/typing.py b/src/typing.py index 1fc43ddd..603383be 100644 --- a/src/typing.py +++ b/src/typing.py @@ -1077,12 +1077,8 @@ def __new__(cls, *args, **kwds): return _generic_new(cls.__next_in_mro__, cls, *args, **kwds) -class _TypingEllipsis: - """Placeholder for ...""" - - -class _TypingEmpty: - """Placeholder for ()""" +class _TypingDummy: + """Placeholder for ... or ()""" class TupleMeta(GenericMeta): @@ -1092,27 +1088,21 @@ class TupleMeta(GenericMeta): def __getitem__(self, parameters): if self.__origin__ is not None or not _geqv(self, Tuple): return super().__getitem__(parameters) + if parameters == (): + result = super().__getitem__((_TypingDummy,)) + result.__args__ = ((),) + return result if not isinstance(parameters, tuple): parameters = (parameters,) if len(parameters) == 2 and parameters[1] == Ellipsis: - use_ellipsis = True - parameters = parameters[:1] + (_TypingEllipsis,) msg = "Tuple[t, ...]: t must be a type." - else: - use_ellipsis = False - msg = "Tuple[t0, t1, ...]: each t must be a type." + p = _type_check(parameters[0], msg) + result = super().__getitem__((p, _TypingDummy)) + result.__args__ = (result.__args__[0], ...) + return result + msg = "Tuple[t0, t1, ...]: each t must be a type." parameters = tuple(_type_check(p, msg) for p in parameters) - if parameters == (): - empty = True - parameters = (_TypingEmpty,) - else: - empty = False - result = super().__getitem__(parameters) - if use_ellipsis: - result.__args__ = result.__args__[:-1] + (...,) - elif empty: - result.__args__ = ((),) - return result + return super().__getitem__(parameters) def __instancecheck__(self, obj): if self.__args__ == None: @@ -1176,16 +1166,15 @@ def __getitem__(self, parameters): raise TypeError("Callable must be used as " "Callable[[arg, ...], result].") args, result = parameters - if args is not Ellipsis: - if not isinstance(args, list): - raise TypeError("Callable[args, result]: " - "args must be a list." - " Got %.100r." % (args,)) if args is Ellipsis: parameters = (Ellipsis, result) elif args == []: parameters = ((), result) else: + if not isinstance(args, list): + raise TypeError("Callable[args, result]: " + "args must be a list." + " Got %.100r." % (args,)) parameters = tuple(args) + (result,) return self.__getitem_inner__(parameters) @@ -1195,25 +1184,18 @@ def __getitem_inner__(self, parameters): result = parameters[-1] msg = "Callable[args, result]: result must be a type." result = _type_check(result, msg) - if args != (Ellipsis,): - use_ellipsis = False - if args != ((),): - empty_args = False - msg = "Callable[[arg, ...], result]: each arg must be a type." - args = tuple(_type_check(arg, msg) for arg in args) - else: - empty_args = True - args = (_TypingEmpty,) - else: - use_ellipsis = True - args = (_TypingEllipsis,) + if args == (Ellipsis,): + res = super().__getitem__((_TypingDummy, result)) + res.__args__ = (..., res.__args__[1]) + return res + if args == ((),): + res = super().__getitem__((_TypingDummy, result)) + res.__args__ = ((), res.__args__[1]) + return res + msg = "Callable[[arg, ...], result]: each arg must be a type." + args = tuple(_type_check(arg, msg) for arg in args) parameters = args + (result,) - result = super().__getitem__(parameters) - if use_ellipsis: - result.__args__ = (...,) + result.__args__[1:] - elif empty_args: - result.__args__ = ((),) + result.__args__[1:] - return result + return super().__getitem__(parameters) class Callable(extra=collections_abc.Callable, metaclass = CallableMeta): From 279a67ed26165e62df53b1d7d0254b865f9a9094 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 25 Oct 2016 02:28:43 +0200 Subject: [PATCH 26/39] Minor changes --- src/typing.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/typing.py b/src/typing.py index 603383be..67bf0762 100644 --- a/src/typing.py +++ b/src/typing.py @@ -609,8 +609,6 @@ class Manager(Employee): pass - You cannot subclass or instantiate a union. - - You cannot write Union[X][Y] (what would it mean?). - - You can use Optional[X] as a shorthand for Union[X, None]. """ @@ -642,8 +640,7 @@ def __new__(cls, parameters=None, origin=None, *args, _root=False): def _eval_type(self, globalns, localns): if self.__args__ is None: return self - ev_args = tuple(_eval_type(t, globalns, localns) - for t in self.__args__) + ev_args = tuple(_eval_type(t, globalns, localns) for t in self.__args__) ev_origin = _eval_type(self.__origin__, globalns, localns) if ev_args == self.__args__ and ev_origin == self.__origin__: return self @@ -939,7 +936,7 @@ def _eval_type(self, globalns, localns): return self.__class__(self.__name__, self.__bases__, dict(self.__dict__), - tvars=self.__parameters__ if self.__origin__ else None, + tvars=_type_vars(ev_args) if ev_args else None, args=ev_args, origin=ev_origin, extra=self.__extra__, From f173fde3fc5525bfc5ee172e2b7f078a0f61a1b5 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 25 Oct 2016 12:43:28 +0200 Subject: [PATCH 27/39] Precalculate hash + refactor + more tests --- src/test_typing.py | 16 +++++ src/typing.py | 144 ++++++++++++++++++++++----------------------- 2 files changed, 87 insertions(+), 73 deletions(-) diff --git a/src/test_typing.py b/src/test_typing.py index 582c6b07..4503f073 100644 --- a/src/test_typing.py +++ b/src/test_typing.py @@ -697,13 +697,26 @@ def test_extended_generic_rules_eq(self): T = TypeVar('T') U = TypeVar('U') self.assertEqual(Tuple[T, T][int], Tuple[int, int]) + self.assertEqual(typing.Iterable[Tuple[T, T]][T], typing.Iterable[Tuple[T, T]]) + with self.assertRaises(TypeError): + Tuple[T, int][()] + with self.assertRaises(TypeError): + Tuple[T, U][T, ...] + self.assertEqual(Union[T, int][int], int) self.assertEqual(Union[T, U][int, Union[int, str]], Union[int, str]) class Base: ... class Derived(Base): ... self.assertEqual(Union[T, Base][Derived], Base) + with self.assertRaises(TypeError): + Union[T, int][1] + self.assertEqual(Callable[[T], T][KT], Callable[[KT], KT]) self.assertEqual(Callable[..., List[T]][int], Callable[..., List[int]]) + with self.assertRaises(TypeError): + Callable[[T], U][..., int] + with self.assertRaises(TypeError): + Callable[[T], U][[], int] def test_extended_generic_rules_repr(self): T = TypeVar('T') @@ -747,6 +760,9 @@ def __call__(self): self.assertIsSubclass(C2, collections_abc.Callable) self.assertIsSubclass(C1, collections_abc.Callable) self.assertIsInstance(T1(), tuple) + self.assertIsSubclass(T2, tuple) + self.assertIsSubclass(Tuple[int, ...], typing.Sequence) + self.assertIsSubclass(Tuple[int, ...], typing.Iterable) def test_fail_with_bare_union(self): with self.assertRaises(TypeError): diff --git a/src/typing.py b/src/typing.py index 67bf0762..751fdb3f 100644 --- a/src/typing.py +++ b/src/typing.py @@ -508,6 +508,40 @@ def __subclasscheck__(self, cls): AnyStr = TypeVar('AnyStr', bytes, str) +def _replace_arg(arg, tvars, args): + if tvars is None: + tvars = [] + if hasattr(arg, '_subs_tree'): + return arg._subs_tree(tvars, args) + if isinstance(arg, TypeVar): + for i, tvar in enumerate(tvars): + if arg == tvar: + return args[i] + return arg + + +def _subs_tree(cls, tvars, args): + if cls.__origin__ is None: + return cls + # Make of chain of origins (i.e. cls -> cls.__origin__) + current = cls.__origin__ + orig_chain = [] + while current.__origin__ is not None: + orig_chain.append(current) + current = current.__origin__ + # Replace type variables in __args__ if asked ... + tree_args = [] + for arg in cls.__args__: + tree_args.append(_replace_arg(arg, tvars, args)) + # ... then continue replacing down the origin chain. + for ocls in orig_chain: + new_tree_args = [] + for i, arg in enumerate(ocls.__args__): + new_tree_args.append(_replace_arg(arg, ocls.__parameters__, tree_args)) + tree_args = new_tree_args + return tree_args + + def _remove_dups_flatten(parameters): # Flatten out Union[Union[...], ...]. params = [] @@ -612,7 +646,7 @@ class Manager(Employee): pass - You can use Optional[X] as a shorthand for Union[X, None]. """ - __slots__ = ('__parameters__', '__args__', '__origin__') + __slots__ = ('__parameters__', '__args__', '__origin__', '__tree_hash__') def __new__(cls, parameters=None, origin=None, *args, _root=False): self = super().__new__(cls, parameters, origin, *args, _root=_root) @@ -620,21 +654,23 @@ def __new__(cls, parameters=None, origin=None, *args, _root=False): self.__parameters__ = None self.__args__ = None self.__origin__ = None + self.__tree_hash__ = hash(frozenset(('Union',))) return self if not isinstance(parameters, tuple): raise TypeError("Expected parameters=") - if origin is not Union: - self.__parameters__ = _type_vars(parameters) - self.__args__ = parameters - self.__origin__ = origin - return self - params = _remove_dups_flatten(parameters) - # It's not a union if there's only one type left. - if len(params) == 1: - return params[0] + if origin is Union: + parameters = _remove_dups_flatten(parameters) + # It's not a union if there's only one type left. + if len(parameters) == 1: + return parameters[0] + self.__parameters__ = _type_vars(parameters) + self.__args__ = parameters self.__origin__ = origin - self.__args__ = params - self.__parameters__ = _type_vars(self.__args__) + subs_tree = self._subs_tree() + if isinstance(subs_tree, tuple): + self.__tree_hash__ = hash(frozenset(subs_tree)) + else: + self.__tree_hash__ = hash(subs_tree) return self def _eval_type(self, globalns, localns): @@ -685,9 +721,9 @@ def __getitem__(self, parameters): return self.__class__(parameters, origin=self, _root=True) def _subs_tree(self, tvars=None, args=None): + if self is Union: + return Union tree_args = _subs_tree(self, tvars, args) - if tree_args is Union: - return (Union,) tree_args = _remove_dups_flatten(tree_args) if len(tree_args) == 1: return tree_args[0] # Union of a single type is that type @@ -696,12 +732,10 @@ def _subs_tree(self, tvars=None, args=None): def __eq__(self, other): if not isinstance(other, _Union): return self._subs_tree() == other - return frozenset(self._subs_tree()) == frozenset(other._subs_tree()) + return self.__tree_hash__ == other.__tree_hash__ def __hash__(self): - if self is Union: - return hash(frozenset((_Union,))) - return hash(frozenset(self._subs_tree())) + return self.__tree_hash__ def __instancecheck__(self, obj): raise TypeError("Unions cannot be used with isinstance().") @@ -753,40 +787,6 @@ def _geqv(a, b): return _gorg(a) is _gorg(b) -def _replace_arg(arg, tvars, args): - if tvars is None: - tvars = [] - if hasattr(arg, '_subs_tree'): - return arg._subs_tree(tvars, args) - if isinstance(arg, TypeVar): - for i, tvar in enumerate(tvars): - if arg == tvar: - return args[i] - return arg - - -def _subs_tree(cls, tvars, args): - if cls.__origin__ is None: - return cls - # Make of chain of origins (i.e. cls -> cls.__origin__) - current = cls.__origin__ - orig_chain = [] - while current.__origin__ is not None: - orig_chain.append(current) - current = current.__origin__ - # Replace type variables in __args__ if asked ... - tree_args = [] - for arg in cls.__args__: - tree_args.append(_replace_arg(arg, tvars, args)) - # ... then continue replacing down the origin chain. - for ocls in orig_chain: - new_tree_args = [] - for i, arg in enumerate(ocls.__args__): - new_tree_args.append(_replace_arg(arg, ocls.__parameters__, tree_args)) - tree_args = new_tree_args - return tree_args - - def _next_in_mro(cls): """Helper for Generic.__new__. @@ -899,7 +899,9 @@ def __new__(cls, name, bases, namespace, self = super().__new__(cls, name, bases, namespace, _root=True) self.__parameters__ = tvars - self.__args__ = args + self.__args__ = tuple(... if a is _TypingEllipsis else + () if a is _TypingEmpty else + a for a in args) if args else None self.__origin__ = origin self.__extra__ = extra # Speed hack (https://github.com/python/typing/issues/196). @@ -920,6 +922,7 @@ def __new__(cls, name, bases, namespace, if origin: self.__qualname__ = origin.__qualname__ + self.__tree_hash__ = hash(self._subs_tree()) if origin else hash((self.__name__,)) return self def _get_type_vars(self, tvars): @@ -959,9 +962,9 @@ def _tree_repr(self, tree): return super().__repr__() + '[%s]' % ', '.join(arg_list) def _subs_tree(self, tvars=None, args=None): - tree_args = _subs_tree(self, tvars, args) - if tree_args is self: + if self.__origin__ is None: return self + tree_args = _subs_tree(self, tvars, args) return (_gorg(self),) + tuple(tree_args) def __eq__(self, other): @@ -969,12 +972,10 @@ def __eq__(self, other): return NotImplemented if self.__origin__ is None or other.__origin__ is None: return self is other - return self._subs_tree() == other._subs_tree() + return self.__tree_hash__ == other.__tree_hash__ def __hash__(self): - if self.__origin__ is None: - return hash((self.__name__,)) - return hash(self._subs_tree()) + return self.__tree_hash__ @_tp_cache def __getitem__(self, params): @@ -1074,8 +1075,12 @@ def __new__(cls, *args, **kwds): return _generic_new(cls.__next_in_mro__, cls, *args, **kwds) -class _TypingDummy: - """Placeholder for ... or ()""" +class _TypingEmpty: + """Placeholder for ()""" + + +class _TypingEllipsis: + """Placeholder for ...""" class TupleMeta(GenericMeta): @@ -1086,17 +1091,13 @@ def __getitem__(self, parameters): if self.__origin__ is not None or not _geqv(self, Tuple): return super().__getitem__(parameters) if parameters == (): - result = super().__getitem__((_TypingDummy,)) - result.__args__ = ((),) - return result + return super().__getitem__((_TypingEmpty,)) if not isinstance(parameters, tuple): parameters = (parameters,) if len(parameters) == 2 and parameters[1] == Ellipsis: msg = "Tuple[t, ...]: t must be a type." p = _type_check(parameters[0], msg) - result = super().__getitem__((p, _TypingDummy)) - result.__args__ = (result.__args__[0], ...) - return result + return super().__getitem__((p, _TypingEllipsis)) msg = "Tuple[t0, t1, ...]: each t must be a type." parameters = tuple(_type_check(p, msg) for p in parameters) return super().__getitem__(parameters) @@ -1182,13 +1183,9 @@ def __getitem_inner__(self, parameters): msg = "Callable[args, result]: result must be a type." result = _type_check(result, msg) if args == (Ellipsis,): - res = super().__getitem__((_TypingDummy, result)) - res.__args__ = (..., res.__args__[1]) - return res + return super().__getitem__((_TypingEllipsis, result)) if args == ((),): - res = super().__getitem__((_TypingDummy, result)) - res.__args__ = ((), res.__args__[1]) - return res + return super().__getitem__((_TypingEmpty, result)) msg = "Callable[[arg, ...], result]: each arg must be a type." args = tuple(_type_check(arg, msg) for arg in args) parameters = args + (result,) @@ -1571,6 +1568,7 @@ def _get_protocol_attrs(self): attr != '__origin__' and attr != '__orig_bases__' and attr != '__extra__' and + attr != '__tree_hash__' and attr != '__module__'): attrs.add(attr) From 819d4721b451c9a9500183d3092bf160d64343e8 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 25 Oct 2016 12:56:09 +0200 Subject: [PATCH 28/39] Simplify repr for Union --- src/typing.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/typing.py b/src/typing.py index 751fdb3f..769ede16 100644 --- a/src/typing.py +++ b/src/typing.py @@ -692,9 +692,7 @@ def __repr__(self): tree = self._subs_tree() if not isinstance(tree, tuple): return repr(tree) - if tree[0] is not Union: - return tree[0]._tree_repr(tree) - return self._tree_repr(tree) + return tree[0]._tree_repr(tree) def _tree_repr(self, tree): arg_list = [] From e6f19a8ea1f1770ed30c0e0ab3afd495d0d5ad6b Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 25 Oct 2016 13:28:33 +0200 Subject: [PATCH 29/39] Add big test for __eq__, __repr__, and Any substitution --- src/test_typing.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/test_typing.py b/src/test_typing.py index 4503f073..e5d66c26 100644 --- a/src/test_typing.py +++ b/src/test_typing.py @@ -799,6 +799,18 @@ class MyDef(typing.DefaultDict[str, T]): ... self.assertIs(MyDef[int]().__class__, MyDef) self.assertIs(MyDef[int]().__orig_class__, MyDef[int]) + def test_all_repr_eq_any(self): + objs = (getattr(typing, el) for el in typing.__all__) + for obj in objs: + self.assertNotEqual(repr(obj), '') + self.assertEqual(obj, obj) + if getattr(obj, '__parameters__', None) and len(obj.__parameters__) == 1: + self.assertEqual(obj[Any].__args__, (Any,)) + if isinstance(obj, type): + for base in obj.__mro__: + self.assertNotEqual(repr(base), '') + self.assertEqual(base, base) + def test_pickle(self): global C # pickle wants to reference the class by name T = TypeVar('T') From deed806d5c21a938260895eec34e1235d1078cd0 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 25 Oct 2016 14:12:28 +0200 Subject: [PATCH 30/39] Add test to illustrate substitution --- src/test_typing.py | 14 ++++++++++++++ src/typing.py | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/test_typing.py b/src/test_typing.py index e5d66c26..5fec03c4 100644 --- a/src/test_typing.py +++ b/src/test_typing.py @@ -811,6 +811,20 @@ def test_all_repr_eq_any(self): self.assertNotEqual(repr(base), '') self.assertEqual(base, base) + def test_substitution_helper(self): + KT = TypeVar('KT') + VT = TypeVar('VT') + class Map(Generic[KT, VT]): + def meth(self, k: KT, v: VT): ... + Alias = Map[T, int] + obj = Alias[str]() + + new_args = typing._subs_tree(obj.__orig_class__) + new_annots = {k: typing._replace_arg(v, type(obj).__parameters__, new_args) + for k, v in obj.meth.__annotations__.items()} + + self.assertEqual(new_annots, {'k': str, 'v': int}) + def test_pickle(self): global C # pickle wants to reference the class by name T = TypeVar('T') diff --git a/src/typing.py b/src/typing.py index 769ede16..4b77fa26 100644 --- a/src/typing.py +++ b/src/typing.py @@ -520,7 +520,7 @@ def _replace_arg(arg, tvars, args): return arg -def _subs_tree(cls, tvars, args): +def _subs_tree(cls, tvars=None, args=None): if cls.__origin__ is None: return cls # Make of chain of origins (i.e. cls -> cls.__origin__) From f6382a79cc29c3350bed53115dab1702fbe8cd9c Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 25 Oct 2016 14:15:03 +0200 Subject: [PATCH 31/39] Minor change in tests --- src/test_typing.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test_typing.py b/src/test_typing.py index 5fec03c4..8e0a8c52 100644 --- a/src/test_typing.py +++ b/src/test_typing.py @@ -812,12 +812,13 @@ def test_all_repr_eq_any(self): self.assertEqual(base, base) def test_substitution_helper(self): + T = TypeVar('T') KT = TypeVar('KT') VT = TypeVar('VT') class Map(Generic[KT, VT]): def meth(self, k: KT, v: VT): ... - Alias = Map[T, int] - obj = Alias[str]() + StrMap = Map[str, T] + obj = StrMap[int]() new_args = typing._subs_tree(obj.__orig_class__) new_annots = {k: typing._replace_arg(v, type(obj).__parameters__, new_args) From 9bc7374b01ad325bf9ca67be683e5a7ae4a008f0 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 25 Oct 2016 16:51:02 +0200 Subject: [PATCH 32/39] Add/expand comments and docstrings --- src/typing.py | 64 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/src/typing.py b/src/typing.py index 4b77fa26..16c325e4 100644 --- a/src/typing.py +++ b/src/typing.py @@ -355,6 +355,7 @@ def _type_check(arg, msg): if (isinstance(arg, _TypingBase) and type(arg).__name__ == '_ClassVar' or not isinstance(arg, (type, _TypingBase)) and not callable(arg)): raise TypeError(msg + " Got %.100r." % (arg,)) + # Bare Union etc. are not valid as type arguments if (type(arg).__name__ in ('_Union', '_Optional') and not getattr(arg, '__origin__', None) or isinstance(arg, TypingMeta) and _gorg(arg) in (Generic, _Protocol)): @@ -374,7 +375,7 @@ def _type_repr(obj): if obj.__module__ == 'builtins': return _qualname(obj) return '%s.%s' % (obj.__module__, _qualname(obj)) - if obj is Ellipsis: + if obj is ...: return('...') if isinstance(obj, types.FunctionType): return obj.__name__ @@ -509,6 +510,11 @@ def __subclasscheck__(self, cls): def _replace_arg(arg, tvars, args): + """ A helper fuunction: replace arg if it is a type variable + found in tvars with corresponding substitution from args or + with corresponding substitution sub-tree if arg is a generic type. + """ + if tvars is None: tvars = [] if hasattr(arg, '_subs_tree'): @@ -521,6 +527,11 @@ def _replace_arg(arg, tvars, args): def _subs_tree(cls, tvars=None, args=None): + """ Calculate substitution tree for generic cls after + replacing its type parameters with substitutions in tvars -> args (if any). + Repeat the same cyclicaly following __origin__'s. + """ + if cls.__origin__ is None: return cls # Make of chain of origins (i.e. cls -> cls.__origin__) @@ -543,6 +554,10 @@ def _subs_tree(cls, tvars=None, args=None): def _remove_dups_flatten(parameters): + """ A helper for Union creation and substitution: flatten Union's + among parameters, then remove duplicates and strict subclasses. + """ + # Flatten out Union[Union[...], ...]. params = [] for p in parameters: @@ -580,7 +595,7 @@ def _remove_dups_flatten(parameters): def _check_generic(cls, parameters): - # Check correct count for generic parameters for a cls + # Check correct count for parameters of a generic cls. if not cls.__parameters__: raise TypeError("%s is not a generic class" % repr(cls)) alen = len(parameters) @@ -591,6 +606,10 @@ def _check_generic(cls, parameters): def _tp_cache(func): + """ Caching for __getitem__ of generic types with a fallback to + original function for non-hashable arguments. + """ + cached = functools.lru_cache()(func) @functools.wraps(func) def inner(*args, **kwds): @@ -666,6 +685,8 @@ def __new__(cls, parameters=None, origin=None, *args, _root=False): self.__parameters__ = _type_vars(parameters) self.__args__ = parameters self.__origin__ = origin + # Pre-calculate the __hash__ on instantiation. + # This improves speed for complex substitutions. subs_tree = self._subs_tree() if isinstance(subs_tree, tuple): self.__tree_hash__ = hash(frozenset(subs_tree)) @@ -679,6 +700,7 @@ def _eval_type(self, globalns, localns): ev_args = tuple(_eval_type(t, globalns, localns) for t in self.__args__) ev_origin = _eval_type(self.__origin__, globalns, localns) if ev_args == self.__args__ and ev_origin == self.__origin__: + # Everything is already evaluated. return self return self.__class__(ev_args, ev_origin, _root=True) @@ -720,7 +742,7 @@ def __getitem__(self, parameters): def _subs_tree(self, tvars=None, args=None): if self is Union: - return Union + return Union # Nothing to substitute tree_args = _subs_tree(self, tvars, args) tree_args = _remove_dups_flatten(tree_args) if len(tree_args) == 1: @@ -897,6 +919,8 @@ def __new__(cls, name, bases, namespace, self = super().__new__(cls, name, bases, namespace, _root=True) self.__parameters__ = tvars + # Be prepared that GenericMeta will be subclassed by TupleMeta + # and CallableMeta, those two allow ..., (), or [] in __args___. self.__args__ = tuple(... if a is _TypingEllipsis else () if a is _TypingEmpty else a for a in args) if args else None @@ -1033,6 +1057,8 @@ def __instancecheck__(self, instance): def _generic_new(base_cls, cls, *args, **kwds): + # Assure type is erased on instantiation, + # but attempt to store it in __orig_class__ if cls.__origin__ is None: return base_cls.__new__(cls) else: @@ -1074,11 +1100,14 @@ def __new__(cls, *args, **kwds): class _TypingEmpty: - """Placeholder for ()""" + """Placeholder for () or []. Used by TupleMeta and CallableMeta + to allow empy list/tuple in specific places, without allowing them + to sneak in where prohibited. + """ class _TypingEllipsis: - """Placeholder for ...""" + """Ditto for ...""" class TupleMeta(GenericMeta): @@ -1087,12 +1116,14 @@ class TupleMeta(GenericMeta): @_tp_cache def __getitem__(self, parameters): if self.__origin__ is not None or not _geqv(self, Tuple): + # Normal generic rules apply if this is not the first subscription + # or a subscription of a subclass. return super().__getitem__(parameters) if parameters == (): return super().__getitem__((_TypingEmpty,)) if not isinstance(parameters, tuple): parameters = (parameters,) - if len(parameters) == 2 and parameters[1] == Ellipsis: + if len(parameters) == 2 and parameters[1] == ...: msg = "Tuple[t, ...]: t must be a type." p = _type_check(parameters[0], msg) return super().__getitem__((p, _TypingEllipsis)) @@ -1133,6 +1164,7 @@ def __new__(cls, *args, **kwds): class CallableMeta(GenericMeta): + """ Metaclass for Callable.""" def __repr__(self): if self.__origin__ is None: @@ -1142,6 +1174,8 @@ def __repr__(self): def _tree_repr(self, tree): if _gorg(self) is not Callable: return super()._tree_repr(tree) + # For actual Callable (not its subclass) we override + # super()._tree_repr() for nice formatting. arg_list = [] for arg in tree[1:]: if arg == (): @@ -1156,33 +1190,35 @@ def _tree_repr(self, tree): '[[%s], %s]' % (', '.join(arg_list[:-1]), arg_list[-1])) def __getitem__(self, parameters): + """ A thin wrapper around __getitem_inner__ to provide the latter + with hashable arguments to improve speed. + """ + if self.__origin__ is not None or not _geqv(self, Callable): return super().__getitem__(parameters) if not isinstance(parameters, tuple) or len(parameters) != 2: raise TypeError("Callable must be used as " "Callable[[arg, ...], result].") args, result = parameters - if args is Ellipsis: - parameters = (Ellipsis, result) + if args is ...: + parameters = (..., result) elif args == []: parameters = ((), result) else: if not isinstance(args, list): - raise TypeError("Callable[args, result]: " - "args must be a list." + raise TypeError("Callable[args, result]: args must be a list." " Got %.100r." % (args,)) parameters = tuple(args) + (result,) return self.__getitem_inner__(parameters) @_tp_cache def __getitem_inner__(self, parameters): - args = parameters[:-1] - result = parameters[-1] + *args, result = parameters msg = "Callable[args, result]: result must be a type." result = _type_check(result, msg) - if args == (Ellipsis,): + if args == [...,]: return super().__getitem__((_TypingEllipsis, result)) - if args == ((),): + if args == [(),]: return super().__getitem__((_TypingEmpty, result)) msg = "Callable[[arg, ...], result]: each arg must be a type." args = tuple(_type_check(arg, msg) for arg in args) From c2724cebda2e2e0d7b1d8d181c1f4da81374b742 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 25 Oct 2016 16:58:16 +0200 Subject: [PATCH 33/39] Fix the __qualname__ in PY 3.2 --- src/typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/typing.py b/src/typing.py index 16c325e4..622fcc15 100644 --- a/src/typing.py +++ b/src/typing.py @@ -942,7 +942,7 @@ def __new__(cls, name, bases, namespace, if isinstance(extra, abc.ABCMeta): self._abc_registry = extra._abc_registry - if origin: + if origin and hasattr(origin, '__qualname__'): # Fix for Python 3.2. self.__qualname__ = origin.__qualname__ self.__tree_hash__ = hash(self._subs_tree()) if origin else hash((self.__name__,)) return self From fd3e3442888cbe23a2d75dc22e8633d14552ca7f Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 25 Oct 2016 20:00:11 +0200 Subject: [PATCH 34/39] Add tests to Python 2 --- python2/test_typing.py | 156 +++++++++++++++++++++++++++++++++++------ 1 file changed, 136 insertions(+), 20 deletions(-) diff --git a/python2/test_typing.py b/python2/test_typing.py index 4b0da3a3..fb41b726 100644 --- a/python2/test_typing.py +++ b/python2/test_typing.py @@ -141,8 +141,9 @@ def test_union_unique(self): self.assertEqual(Union[X, X], X) self.assertNotEqual(Union[X, int], Union[X]) self.assertNotEqual(Union[X, int], Union[int]) - self.assertEqual(Union[X, int].__union_params__, (X, int)) - self.assertEqual(Union[X, int].__union_set_params__, {X, int}) + self.assertEqual(Union[X, int].__args__, (X, int)) + self.assertEqual(Union[X, int].__parameters__, (X,)) + self.assertIs(Union[X, int].__origin__, Union) def test_union_constrained(self): A = TypeVar('A', str, bytes) @@ -308,8 +309,6 @@ def Elem(*args): class TupleTests(BaseTestCase): def test_basics(self): - with self.assertRaises(TypeError): - issubclass(Tuple[int, str], Tuple) with self.assertRaises(TypeError): issubclass(Tuple, Tuple[int, str]) with self.assertRaises(TypeError): @@ -364,22 +363,6 @@ def test_eq_hash(self): self.assertNotEqual(Callable[[int], int], Callable[[], int]) self.assertNotEqual(Callable[[int], int], Callable) - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - - class C(Callable): - pass - - with self.assertRaises(TypeError): - - class C(type(Callable)): - pass - - with self.assertRaises(TypeError): - - class C(Callable[[int], int]): - pass - def test_cannot_instantiate(self): with self.assertRaises(TypeError): Callable() @@ -683,6 +666,139 @@ class D(C, List[T][U][V]): pass self.assertEqual(C.__orig_bases__, (List[T][U][V],)) self.assertEqual(D.__orig_bases__, (C, List[T][U][V])) + def test_extended_generic_rules_eq(self): + T = TypeVar('T') + U = TypeVar('U') + self.assertEqual(Tuple[T, T][int], Tuple[int, int]) + self.assertEqual(typing.Iterable[Tuple[T, T]][T], typing.Iterable[Tuple[T, T]]) + with self.assertRaises(TypeError): + Tuple[T, int][()] + with self.assertRaises(TypeError): + Tuple[T, U][T, ...] + + self.assertEqual(Union[T, int][int], int) + self.assertEqual(Union[T, U][int, Union[int, str]], Union[int, str]) + class Base: pass + class Derived(Base): pass + self.assertEqual(Union[T, Base][Derived], Base) + with self.assertRaises(TypeError): + Union[T, int][1] + + self.assertEqual(Callable[[T], T][KT], Callable[[KT], KT]) + self.assertEqual(Callable[..., List[T]][int], Callable[..., List[int]]) + with self.assertRaises(TypeError): + Callable[[T], U][..., int] + with self.assertRaises(TypeError): + Callable[[T], U][[], int] + + def test_extended_generic_rules_repr(self): + T = TypeVar('T') + self.assertEqual(repr(Union[Tuple, Callable]).replace('typing.', ''), + 'Union[Tuple, Callable]') + self.assertEqual(repr(Union[Tuple, Tuple[int]]).replace('typing.', ''), + 'Tuple') + self.assertEqual(repr(Callable[..., Optional[T]][int]).replace('typing.', ''), + 'Callable[..., Union[int, NoneType]]') + self.assertEqual(repr(Callable[[], List[T]][int]).replace('typing.', ''), + 'Callable[[], List[int]]') + + def test_generic_forvard_ref(self): + def foobar(x: List[List['T']]): pass + T = TypeVar('T') + self.assertEqual(get_type_hints(foobar, globals(), locals()), {'x': List[List[T]]}) + def barfoo(x: Tuple[T, ...]): pass + self.assertIs(get_type_hints(barfoo, globals(), locals())['x'], Tuple[T, ...]) + + def test_extended_generic_rules_subclassing(self): + class T1(Tuple[T, KT]): pass + class T2(Tuple[T, ...]): pass + class C1(Callable[[T], T]): pass + class C2(Callable[..., int]): + def __call__(self): + return None + + self.assertEqual(T1.__parameters__, (T, KT)) + self.assertEqual(T1[int, str].__args__, (int, str)) + self.assertEqual(T1[int, T].__origin__, T1) + + self.assertEqual(T2.__parameters__, (T,)) + with self.assertRaises(TypeError): + T1[int] + with self.assertRaises(TypeError): + T2[int, str] + + self.assertEqual(repr(C1[int]).split('.')[-1], 'C1[int]') + self.assertEqual(C2.__parameters__, ()) + self.assertIsInstance(C2(), collections_abc.Callable) + self.assertIsSubclass(C2, collections_abc.Callable) + self.assertIsSubclass(C1, collections_abc.Callable) + self.assertIsInstance(T1(), tuple) + self.assertIsSubclass(T2, tuple) + self.assertIsSubclass(Tuple[int, ...], typing.Sequence) + self.assertIsSubclass(Tuple[int, ...], typing.Iterable) + + def test_fail_with_bare_union(self): + with self.assertRaises(TypeError): + List[Union] + with self.assertRaises(TypeError): + Tuple[Optional] + with self.assertRaises(TypeError): + ClassVar[ClassVar] + with self.assertRaises(TypeError): + List[ClassVar[int]] + + def test_fail_with_bare_generic(self): + T = TypeVar('T') + with self.assertRaises(TypeError): + List[Generic] + with self.assertRaises(TypeError): + Tuple[Generic[T]] + with self.assertRaises(TypeError): + List[typing._Protocol] + + def test_type_erasure_special(self): + T = TypeVar('T') + class MyTup(Tuple[T, T]): pass + self.assertIs(MyTup[int]().__class__, MyTup) + self.assertIs(MyTup[int]().__orig_class__, MyTup[int]) + class MyCall(Callable[..., T]): + def __call__(self): return None + self.assertIs(MyCall[T]().__class__, MyCall) + self.assertIs(MyCall[T]().__orig_class__, MyCall[T]) + class MyDict(typing.Dict[T, T]): pass + self.assertIs(MyDict[int]().__class__, MyDict) + self.assertIs(MyDict[int]().__orig_class__, MyDict[int]) + class MyDef(typing.DefaultDict[str, T]): pass + self.assertIs(MyDef[int]().__class__, MyDef) + self.assertIs(MyDef[int]().__orig_class__, MyDef[int]) + + def test_all_repr_eq_any(self): + objs = (getattr(typing, el) for el in typing.__all__) + for obj in objs: + self.assertNotEqual(repr(obj), '') + self.assertEqual(obj, obj) + if getattr(obj, '__parameters__', None) and len(obj.__parameters__) == 1: + self.assertEqual(obj[Any].__args__, (Any,)) + if isinstance(obj, type): + for base in obj.__mro__: + self.assertNotEqual(repr(base), '') + self.assertEqual(base, base) + + def test_substitution_helper(self): + T = TypeVar('T') + KT = TypeVar('KT') + VT = TypeVar('VT') + class Map(Generic[KT, VT]): + def meth(self, k: KT, v: VT): pass + StrMap = Map[str, T] + obj = StrMap[int]() + + new_args = typing._subs_tree(obj.__orig_class__) + new_annots = {k: typing._replace_arg(v, type(obj).__parameters__, new_args) + for k, v in obj.meth.__annotations__.items()} + + self.assertEqual(new_annots, {'k': str, 'v': int}) + def test_pickle(self): global C # pickle wants to reference the class by name T = TypeVar('T') From cd7bbe2329ac7c99e135dd7870c030112ef354d1 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 25 Oct 2016 20:16:00 +0200 Subject: [PATCH 35/39] Modify Union in Python 2 --- python2/test_typing.py | 2 +- python2/typing.py | 264 ++++++++++++++++++++++++++++------------- src/typing.py | 2 +- 3 files changed, 182 insertions(+), 86 deletions(-) diff --git a/python2/test_typing.py b/python2/test_typing.py index fb41b726..f75fb8c5 100644 --- a/python2/test_typing.py +++ b/python2/test_typing.py @@ -840,7 +840,7 @@ class C(Generic[T]): X = C[int] self.assertEqual(X.__module__, __name__) if not PY32: - self.assertEqual(X.__qualname__, 'C') + self.assertTrue(X.__qualname__.endswith('..C')) self.assertEqual(repr(X).split('.')[-1], 'C[int]') class Y(C[int]): diff --git a/python2/typing.py b/python2/typing.py index f19b8c78..95819463 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -309,8 +309,7 @@ def _type_vars(types): def _eval_type(t, globalns, localns): if isinstance(t, TypingMeta) or isinstance(t, _TypingBase): return t._eval_type(globalns, localns) - else: - return t + return t def _type_check(arg, msg): @@ -327,10 +326,16 @@ def _type_check(arg, msg): """ if arg is None: return type(None) - if isinstance(arg, basestring): + if isinstance(arg, str): arg = _ForwardRef(arg) - if not isinstance(arg, (type, _TypingBase)) and not callable(arg): + if (isinstance(arg, _TypingBase) and type(arg).__name__ == '_ClassVar' or + not isinstance(arg, (type, _TypingBase)) and not callable(arg)): raise TypeError(msg + " Got %.100r." % (arg,)) + # Bare Union etc. are not valid as type arguments + if (type(arg).__name__ in ('_Union', '_Optional') + and not getattr(arg, '__origin__', None) + or isinstance(arg, TypingMeta) and _gorg(arg) in (Generic, _Protocol)): + raise TypeError("Plain %s is not valid as type argument" % arg) return arg @@ -345,10 +350,12 @@ def _type_repr(obj): if isinstance(obj, type) and not isinstance(obj, TypingMeta): if obj.__module__ == '__builtin__': return _qualname(obj) - else: - return '%s.%s' % (obj.__module__, _qualname(obj)) - else: - return repr(obj) + return '%s.%s' % (obj.__module__, _qualname(obj)) + if obj is Ellipsis: + return('...') + if isinstance(obj, types.FunctionType): + return obj.__name__ + return repr(obj) class ClassVarMeta(TypingMeta): @@ -396,17 +403,10 @@ def _eval_type(self, globalns, localns): return type(self)(_eval_type(self.__type__, globalns, localns), _root=True) - def _get_type_vars(self, tvars): - if self.__type__: - _get_type_vars([self.__type__], tvars) - def __repr__(self): - return self._subs_repr([], []) - - def _subs_repr(self, tvars, args): r = super(_ClassVar, self).__repr__() if self.__type__ is not None: - r += '[{}]'.format(_replace_arg(self.__type__, tvars, args)) + r += '[{}]'.format(_type_repr(self.__type__)) return r def __hash__(self): @@ -566,6 +566,102 @@ def __subclasscheck__(self, cls): AnyStr = TypeVar('AnyStr', bytes, unicode) +def _replace_arg(arg, tvars, args): + """ A helper fuunction: replace arg if it is a type variable + found in tvars with corresponding substitution from args or + with corresponding substitution sub-tree if arg is a generic type. + """ + + if tvars is None: + tvars = [] + if hasattr(arg, '_subs_tree'): + return arg._subs_tree(tvars, args) + if isinstance(arg, TypeVar): + for i, tvar in enumerate(tvars): + if arg == tvar: + return args[i] + return arg + + +def _subs_tree(cls, tvars=None, args=None): + """ Calculate substitution tree for generic cls after + replacing its type parameters with substitutions in tvars -> args (if any). + Repeat the same cyclicaly following __origin__'s. + """ + + if cls.__origin__ is None: + return cls + # Make of chain of origins (i.e. cls -> cls.__origin__) + current = cls.__origin__ + orig_chain = [] + while current.__origin__ is not None: + orig_chain.append(current) + current = current.__origin__ + # Replace type variables in __args__ if asked ... + tree_args = [] + for arg in cls.__args__: + tree_args.append(_replace_arg(arg, tvars, args)) + # ... then continue replacing down the origin chain. + for ocls in orig_chain: + new_tree_args = [] + for i, arg in enumerate(ocls.__args__): + new_tree_args.append(_replace_arg(arg, ocls.__parameters__, tree_args)) + tree_args = new_tree_args + return tree_args + + +def _remove_dups_flatten(parameters): + """ A helper for Union creation and substitution: flatten Union's + among parameters, then remove duplicates and strict subclasses. + """ + + # Flatten out Union[Union[...], ...]. + params = [] + for p in parameters: + if isinstance(p, _Union) and p.__origin__ is Union: + params.extend(p.__args__) + elif isinstance(p, tuple) and len(p) > 0 and p[0] is Union: + params.extend(p[1:]) + else: + params.append(p) + # Weed out strict duplicates, preserving the first of each occurrence. + all_params = set(params) + if len(all_params) < len(params): + new_params = [] + for t in params: + if t in all_params: + new_params.append(t) + all_params.remove(t) + params = new_params + assert not all_params, all_params + # Weed out subclasses. + # E.g. Union[int, Employee, Manager] == Union[int, Employee]. + # If object is present it will be sole survivor among proper classes. + # Never discard type variables. + # (In particular, Union[str, AnyStr] != AnyStr.) + all_params = set(params) + for t1 in params: + if not isinstance(t1, type): + continue + if any(isinstance(t2, type) and issubclass(t1, t2) + for t2 in all_params - {t1} + if not (isinstance(t2, GenericMeta) and + t2.__origin__ is not None)): + all_params.remove(t1) + return tuple(t for t in params if t in all_params) + + +def _check_generic(cls, parameters): + # Check correct count for parameters of a generic cls. + if not cls.__parameters__: + raise TypeError("%s is not a generic class" % repr(cls)) + alen = len(parameters) + elen = len(cls.__parameters__) + if alen != elen: + raise TypeError("Too %s parameters for %s; actual %s, expected %s" % + ("many" if alen > elen else "few", repr(cls), alen, elen)) + + def _tp_cache(func): maxsize = 128 cache = {} @@ -638,101 +734,101 @@ class Manager(Employee): pass - You cannot subclass or instantiate a union. - - You cannot write Union[X][Y] (what would it mean?). - - You can use Optional[X] as a shorthand for Union[X, None]. """ __metaclass__ = UnionMeta - __slots__ = ('__union_params__', '__union_set_params__') - - def __new__(cls, parameters=None, *args, **kwds): - self = super(_Union, cls).__new__(cls, parameters, *args, **kwds) - if parameters is None: - self.__union_params__ = None - self.__union_set_params__ = None + __slots__ = ('__parameters__', '__args__', '__origin__', '__tree_hash__') + + def __new__(cls, parameters=None, origin=None, *args, _root=False): + self = super(_Union, cls).__new__(cls, parameters, origin, *args, _root=_root) + if origin is None: + self.__parameters__ = None + self.__args__ = None + self.__origin__ = None + self.__tree_hash__ = hash(frozenset(('Union',))) return self if not isinstance(parameters, tuple): raise TypeError("Expected parameters=") - # Flatten out Union[Union[...], ...] and type-check non-Union args. - params = [] - msg = "Union[arg, ...]: each arg must be a type." - for p in parameters: - if isinstance(p, _Union): - params.extend(p.__union_params__) - else: - params.append(_type_check(p, msg)) - # Weed out strict duplicates, preserving the first of each occurrence. - all_params = set(params) - if len(all_params) < len(params): - new_params = [] - for t in params: - if t in all_params: - new_params.append(t) - all_params.remove(t) - params = new_params - assert not all_params, all_params - # Weed out subclasses. - # E.g. Union[int, Employee, Manager] == Union[int, Employee]. - # If object is present it will be sole survivor among proper classes. - # Never discard type variables. - # (In particular, Union[str, AnyStr] != AnyStr.) - all_params = set(params) - for t1 in params: - if not isinstance(t1, type): - continue - if any(isinstance(t2, type) and issubclass(t1, t2) - for t2 in all_params - {t1} - if not (isinstance(t2, GenericMeta) and - t2.__origin__ is not None)): - all_params.remove(t1) - # It's not a union if there's only one type left. - if len(all_params) == 1: - return all_params.pop() - self.__union_params__ = tuple(t for t in params if t in all_params) - self.__union_set_params__ = frozenset(self.__union_params__) + if origin is Union: + parameters = _remove_dups_flatten(parameters) + # It's not a union if there's only one type left. + if len(parameters) == 1: + return parameters[0] + self.__parameters__ = _type_vars(parameters) + self.__args__ = parameters + self.__origin__ = origin + # Pre-calculate the __hash__ on instantiation. + # This improves speed for complex substitutions. + subs_tree = self._subs_tree() + if isinstance(subs_tree, tuple): + self.__tree_hash__ = hash(frozenset(subs_tree)) + else: + self.__tree_hash__ = hash(subs_tree) return self def _eval_type(self, globalns, localns): - p = tuple(_eval_type(t, globalns, localns) - for t in self.__union_params__) - if p == self.__union_params__: + if self.__args__ is None: return self - else: - return self.__class__(p, _root=True) + ev_args = tuple(_eval_type(t, globalns, localns) for t in self.__args__) + ev_origin = _eval_type(self.__origin__, globalns, localns) + if ev_args == self.__args__ and ev_origin == self.__origin__: + # Everything is already evaluated. + return self + return self.__class__(ev_args, ev_origin, _root=True) def _get_type_vars(self, tvars): - if self.__union_params__: - _get_type_vars(self.__union_params__, tvars) + if self.__origin__ and self.__parameters__: + _get_type_vars(self.__parameters__, tvars) def __repr__(self): - return self._subs_repr([], []) - - def _subs_repr(self, tvars, args): - r = super(_Union, self).__repr__() - if self.__union_params__: - r += '[%s]' % (', '.join(_replace_arg(t, tvars, args) - for t in self.__union_params__)) - return r + if self.__origin__ is None: + return super(_Union, self).__repr__() + tree = self._subs_tree() + if not isinstance(tree, tuple): + return repr(tree) + return tree[0]._tree_repr(tree) + + def _tree_repr(self, tree): + arg_list = [] + for arg in tree[1:]: + if not isinstance(arg, tuple): + arg_list.append(_type_repr(arg)) + else: + arg_list.append(arg[0]._tree_repr(arg)) + return super(_Union, self).__repr__() + '[%s]' % ', '.join(arg_list) @_tp_cache def __getitem__(self, parameters): - if self.__union_params__ is not None: - raise TypeError( - "Cannot subscript an existing Union. Use Union[u, t] instead.") if parameters == (): raise TypeError("Cannot take a Union of no types.") if not isinstance(parameters, tuple): parameters = (parameters,) - return self.__class__(parameters, _root=True) + if self.__origin__ is None: + msg = "Union[arg, ...]: each arg must be a type." + else: + msg = "Parameters to generic types must be types." + parameters = tuple(_type_check(p, msg) for p in parameters) + if self is not Union: + _check_generic(self, parameters) + return self.__class__(parameters, origin=self, _root=True) + + def _subs_tree(self, tvars=None, args=None): + if self is Union: + return Union # Nothing to substitute + tree_args = _subs_tree(self, tvars, args) + tree_args = _remove_dups_flatten(tree_args) + if len(tree_args) == 1: + return tree_args[0] # Union of a single type is that type + return (Union,) + tree_args def __eq__(self, other): if not isinstance(other, _Union): - return NotImplemented - return self.__union_set_params__ == other.__union_set_params__ + return self._subs_tree() == other + return self.__tree_hash__ == other.__tree_hash__ def __hash__(self): - return hash(self.__union_set_params__) + return self.__tree_hash__ def __instancecheck__(self, obj): raise TypeError("Unions cannot be used with isinstance().") diff --git a/src/typing.py b/src/typing.py index 622fcc15..b787d71b 100644 --- a/src/typing.py +++ b/src/typing.py @@ -356,7 +356,7 @@ def _type_check(arg, msg): not isinstance(arg, (type, _TypingBase)) and not callable(arg)): raise TypeError(msg + " Got %.100r." % (arg,)) # Bare Union etc. are not valid as type arguments - if (type(arg).__name__ in ('_Union', '_Optional') + if (type(arg).__name__ in ('_Union', '_Optional') and not getattr(arg, '__origin__', None) or isinstance(arg, TypingMeta) and _gorg(arg) in (Generic, _Protocol)): raise TypeError("Plain %s is not valid as type argument" % arg) From 068ab5eb2f1db61c796edd0c944a9e6a4bf3552f Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 25 Oct 2016 20:23:19 +0200 Subject: [PATCH 36/39] Update Python 2 tests --- python2/test_typing.py | 23 ++++------------------- python2/typing.py | 4 ++-- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/python2/test_typing.py b/python2/test_typing.py index f75fb8c5..c9effdab 100644 --- a/python2/test_typing.py +++ b/python2/test_typing.py @@ -703,11 +703,11 @@ def test_extended_generic_rules_repr(self): 'Callable[[], List[int]]') def test_generic_forvard_ref(self): - def foobar(x: List[List['T']]): pass + LLT = List[List['T']] T = TypeVar('T') - self.assertEqual(get_type_hints(foobar, globals(), locals()), {'x': List[List[T]]}) - def barfoo(x: Tuple[T, ...]): pass - self.assertIs(get_type_hints(barfoo, globals(), locals())['x'], Tuple[T, ...]) + self.assertEqual(typing._eval_type(LLT, globals(), locals()), List[List[T]]) + TTE = Tuple[T, ...] + self.assertIs(typing._eval_type(TTE, globals(), locals()), Tuple[T, ...]) def test_extended_generic_rules_subclassing(self): class T1(Tuple[T, KT]): pass @@ -784,21 +784,6 @@ def test_all_repr_eq_any(self): self.assertNotEqual(repr(base), '') self.assertEqual(base, base) - def test_substitution_helper(self): - T = TypeVar('T') - KT = TypeVar('KT') - VT = TypeVar('VT') - class Map(Generic[KT, VT]): - def meth(self, k: KT, v: VT): pass - StrMap = Map[str, T] - obj = StrMap[int]() - - new_args = typing._subs_tree(obj.__orig_class__) - new_annots = {k: typing._replace_arg(v, type(obj).__parameters__, new_args) - for k, v in obj.meth.__annotations__.items()} - - self.assertEqual(new_annots, {'k': str, 'v': int}) - def test_pickle(self): global C # pickle wants to reference the class by name T = TypeVar('T') diff --git a/python2/typing.py b/python2/typing.py index 95819463..b56db6e5 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -740,8 +740,8 @@ class Manager(Employee): pass __metaclass__ = UnionMeta __slots__ = ('__parameters__', '__args__', '__origin__', '__tree_hash__') - def __new__(cls, parameters=None, origin=None, *args, _root=False): - self = super(_Union, cls).__new__(cls, parameters, origin, *args, _root=_root) + def __new__(cls, parameters=None, origin=None, *args, **kwds): + self = super(_Union, cls).__new__(cls, parameters, origin, *args, **kwds) if origin is None: self.__parameters__ = None self.__args__ = None From 09c47977348e718a7e03f4f1e963afb8ebb5be82 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 25 Oct 2016 21:11:56 +0200 Subject: [PATCH 37/39] Change remaining parts in Python 2 --- python2/test_typing.py | 2 +- python2/typing.py | 497 +++++++++++++++++++---------------------- src/typing.py | 2 +- 3 files changed, 229 insertions(+), 272 deletions(-) diff --git a/python2/test_typing.py b/python2/test_typing.py index c9effdab..07a0e1fa 100644 --- a/python2/test_typing.py +++ b/python2/test_typing.py @@ -678,7 +678,7 @@ def test_extended_generic_rules_eq(self): self.assertEqual(Union[T, int][int], int) self.assertEqual(Union[T, U][int, Union[int, str]], Union[int, str]) - class Base: pass + class Base(object): pass class Derived(Base): pass self.assertEqual(Union[T, Base][Derived], Base) with self.assertRaises(TypeError): diff --git a/python2/typing.py b/python2/typing.py index b56db6e5..118230cd 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -326,7 +326,7 @@ def _type_check(arg, msg): """ if arg is None: return type(None) - if isinstance(arg, str): + if isinstance(arg, basestring): arg = _ForwardRef(arg) if (isinstance(arg, _TypingBase) and type(arg).__name__ == '_ClassVar' or not isinstance(arg, (type, _TypingBase)) and not callable(arg)): @@ -866,212 +866,6 @@ def __getitem__(self, arg): Optional = _Optional(_root=True) -class TupleMeta(TypingMeta): - """Metaclass for Tuple.""" - - def __new__(cls, name, bases, namespace): - cls.assert_no_subclassing(bases) - return super(TupleMeta, cls).__new__(cls, name, bases, namespace) - - -class _Tuple(_FinalTypingBase): - """Tuple type; Tuple[X, Y] is the cross-product type of X and Y. - - Example: Tuple[T1, T2] is a tuple of two elements corresponding - to type variables T1 and T2. Tuple[int, float, str] is a tuple - of an int, a float and a string. - - To specify a variable-length tuple of homogeneous type, use Tuple[T, ...]. - """ - - __metaclass__ = TupleMeta - __slots__ = ('__tuple_params__', '__tuple_use_ellipsis__') - - def __init__(self, parameters=None, - use_ellipsis=False, _root=False): - self.__tuple_params__ = parameters - self.__tuple_use_ellipsis__ = use_ellipsis - - def _get_type_vars(self, tvars): - if self.__tuple_params__: - _get_type_vars(self.__tuple_params__, tvars) - - def _eval_type(self, globalns, localns): - tp = self.__tuple_params__ - if tp is None: - return self - p = tuple(_eval_type(t, globalns, localns) for t in tp) - if p == self.__tuple_params__: - return self - else: - return self.__class__(p, _root=True) - - def __repr__(self): - return self._subs_repr([], []) - - def _subs_repr(self, tvars, args): - r = super(_Tuple, self).__repr__() - if self.__tuple_params__ is not None: - params = [_replace_arg(p, tvars, args) for p in self.__tuple_params__] - if self.__tuple_use_ellipsis__: - params.append('...') - if not params: - params.append('()') - r += '[%s]' % ( - ', '.join(params)) - return r - - @_tp_cache - def __getitem__(self, parameters): - if self.__tuple_params__ is not None: - raise TypeError("Cannot re-parameterize %r" % (self,)) - if not isinstance(parameters, tuple): - parameters = (parameters,) - if len(parameters) == 2 and parameters[1] == Ellipsis: - parameters = parameters[:1] - use_ellipsis = True - msg = "Tuple[t, ...]: t must be a type." - else: - use_ellipsis = False - msg = "Tuple[t0, t1, ...]: each t must be a type." - parameters = tuple(_type_check(p, msg) for p in parameters) - return self.__class__(parameters, use_ellipsis=use_ellipsis, _root=True) - - def __eq__(self, other): - if not isinstance(other, _Tuple): - return NotImplemented - return (self.__tuple_params__ == other.__tuple_params__ and - self.__tuple_use_ellipsis__ == other.__tuple_use_ellipsis__) - - def __hash__(self): - return hash(self.__tuple_params__) - - def __instancecheck__(self, obj): - if self.__tuple_params__ == None: - return isinstance(obj, tuple) - raise TypeError("Parameterized Tuple cannot be used " - "with isinstance().") - - def __subclasscheck__(self, cls): - if self.__tuple_params__ == None: - return issubclass(cls, tuple) - raise TypeError("Parameterized Tuple cannot be used " - "with issubclass().") - - -Tuple = _Tuple(_root=True) - - -class CallableMeta(TypingMeta): - """Metaclass for Callable.""" - - def __new__(cls, name, bases, namespace): - cls.assert_no_subclassing(bases) - return super(CallableMeta, cls).__new__(cls, name, bases, namespace) - - -class _Callable(_FinalTypingBase): - """Callable type; Callable[[int], str] is a function of (int) -> str. - - The subscription syntax must always be used with exactly two - values: the argument list and the return type. The argument list - must be a list of types; the return type must be a single type. - - There is no syntax to indicate optional or keyword arguments, - such function types are rarely used as callback types. - """ - - __metaclass__ = CallableMeta - __slots__ = ('__args__', '__result__') - - def __init__(self, args=None, result=None, _root=False): - if args is None and result is None: - pass # Must be 'class Callable'. - else: - if args is not Ellipsis: - if not isinstance(args, list): - raise TypeError("Callable[args, result]: " - "args must be a list." - " Got %.100r." % (args,)) - msg = "Callable[[arg, ...], result]: each arg must be a type." - args = tuple(_type_check(arg, msg) for arg in args) - msg = "Callable[args, result]: result must be a type." - result = _type_check(result, msg) - self.__args__ = args - self.__result__ = result - - def _get_type_vars(self, tvars): - if self.__args__ and self.__args__ is not Ellipsis: - _get_type_vars(self.__args__, tvars) - if self.__result__: - _get_type_vars([self.__result__], tvars) - - def _eval_type(self, globalns, localns): - if self.__args__ is None and self.__result__ is None: - return self - if self.__args__ is Ellipsis: - args = self.__args__ - else: - args = [_eval_type(t, globalns, localns) for t in self.__args__] - result = _eval_type(self.__result__, globalns, localns) - if args == self.__args__ and result == self.__result__: - return self - else: - return self.__class__(args=args, result=result, _root=True) - - def __repr__(self): - return self._subs_repr([], []) - - def _subs_repr(self, tvars, args): - r = super(_Callable, self).__repr__() - if self.__args__ is not None or self.__result__ is not None: - if self.__args__ is Ellipsis: - args_r = '...' - else: - args_r = '[%s]' % ', '.join(_replace_arg(t, tvars, args) - for t in self.__args__) - r += '[%s, %s]' % (args_r, _replace_arg(self.__result__, tvars, args)) - return r - - def __getitem__(self, parameters): - if self.__args__ is not None or self.__result__ is not None: - raise TypeError("This Callable type is already parameterized.") - if not isinstance(parameters, tuple) or len(parameters) != 2: - raise TypeError( - "Callable must be used as Callable[[arg, ...], result].") - args, result = parameters - return self.__class__(args=args, result=result, _root=True) - - def __eq__(self, other): - if not isinstance(other, _Callable): - return NotImplemented - return (self.__args__ == other.__args__ and - self.__result__ == other.__result__) - - def __hash__(self): - return hash(self.__args__) ^ hash(self.__result__) - - def __instancecheck__(self, obj): - # For unparametrized Callable we allow this, because - # typing.Callable should be equivalent to - # collections.abc.Callable. - if self.__args__ is None and self.__result__ is None: - return isinstance(obj, collections_abc.Callable) - else: - raise TypeError("Parameterized Callable cannot be used " - "with isinstance().") - - def __subclasscheck__(self, cls): - if self.__args__ is None and self.__result__ is None: - return issubclass(cls, collections_abc.Callable) - else: - raise TypeError("Parameterized Callable cannot be used " - "with issubclass().") - - -Callable = _Callable(_root=True) - - def _gorg(a): """Return the farthest origin of a generic class.""" assert isinstance(a, GenericMeta) @@ -1095,16 +889,6 @@ def _geqv(a, b): return _gorg(a) is _gorg(b) -def _replace_arg(arg, tvars, args): - if hasattr(arg, '_subs_repr'): - return arg._subs_repr(tvars, args) - if isinstance(arg, TypeVar): - for i, tvar in enumerate(tvars): - if arg == tvar: - return args[i] - return _type_repr(arg) - - def _next_in_mro(cls): """Helper for Generic.__new__. @@ -1219,7 +1003,11 @@ def __new__(cls, name, bases, namespace, self = super(GenericMeta, cls).__new__(cls, name, bases, namespace) self.__parameters__ = tvars - self.__args__ = args + # Be prepared that GenericMeta will be subclassed by TupleMeta + # and CallableMeta, those two allow ..., (), or [] in __args___. + self.__args__ = tuple(Ellipsis if a is _TypingEllipsis else + () if a is _TypingEmpty else + a for a in args) if args else None self.__origin__ = origin self.__extra__ = extra # Speed hack (https://github.com/python/typing/issues/196). @@ -1237,55 +1025,69 @@ def __new__(cls, name, bases, namespace, self.__subclasshook__ = _make_subclasshook(self) if isinstance(extra, abc.ABCMeta): self._abc_registry = extra._abc_registry + + if origin and hasattr(origin, '__qualname__'): # Fix for Python 3.2. + self.__qualname__ = origin.__qualname__ + self.__tree_hash__ = hash(self._subs_tree()) if origin else hash((self.__name__,)) return self def _get_type_vars(self, tvars): if self.__origin__ and self.__parameters__: _get_type_vars(self.__parameters__, tvars) + def _eval_type(self, globalns, localns): + ev_origin = (self.__origin__._eval_type(globalns, localns) + if self.__origin__ else None) + ev_args = tuple(_eval_type(a, globalns, localns) for a + in self.__args__) if self.__args__ else None + if ev_origin == self.__origin__ and ev_args == self.__args__: + return self + return self.__class__(self.__name__, + self.__bases__, + dict(self.__dict__), + tvars=_type_vars(ev_args) if ev_args else None, + args=ev_args, + origin=ev_origin, + extra=self.__extra__, + orig_bases=self.__orig_bases__) + def __repr__(self): if self.__origin__ is None: return super(GenericMeta, self).__repr__() - return self._subs_repr([], []) - - def _subs_repr(self, tvars, args): - assert len(tvars) == len(args) - # Construct the chain of __origin__'s. - current = self.__origin__ - orig_chain = [] - while current.__origin__ is not None: - orig_chain.append(current) - current = current.__origin__ - # Replace type variables in __args__ if asked ... - str_args = [] - for arg in self.__args__: - str_args.append(_replace_arg(arg, tvars, args)) - # ... then continue replacing down the origin chain. - for cls in orig_chain: - new_str_args = [] - for i, arg in enumerate(cls.__args__): - new_str_args.append(_replace_arg(arg, cls.__parameters__, str_args)) - str_args = new_str_args - return super(GenericMeta, self).__repr__() + '[%s]' % ', '.join(str_args) + return self._tree_repr(self._subs_tree()) + + def _tree_repr(self, tree): + arg_list = [] + for arg in tree[1:]: + if arg == (): + arg_list.append('()') + elif not isinstance(arg, tuple): + arg_list.append(_type_repr(arg)) + else: + arg_list.append(arg[0]._tree_repr(arg)) + return super(GenericMeta, self).__repr__() + '[%s]' % ', '.join(arg_list) + + 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) def __eq__(self, other): if not isinstance(other, GenericMeta): return NotImplemented - if self.__origin__ is not None: - return (self.__origin__ is other.__origin__ and - self.__args__ == other.__args__ and - self.__parameters__ == other.__parameters__) - else: + if self.__origin__ is None or other.__origin__ is None: return self is other + return self.__tree_hash__ == other.__tree_hash__ def __hash__(self): - return hash((self.__name__, self.__parameters__)) + return self.__tree_hash__ @_tp_cache def __getitem__(self, params): if not isinstance(params, tuple): params = (params,) - if not params: + if not params and not _gorg(self) is Tuple: raise TypeError( "Parameter list to %s[...] cannot be empty" % _qualname(self)) msg = "Parameters to generic types must be types." @@ -1300,6 +1102,9 @@ def __getitem__(self, params): "Parameters to Generic[...] must all be unique") tvars = params args = params + elif self in (Tuple, Callable): + tvars = _type_vars(params) + args = params elif self is _Protocol: # _Protocol is internal, don't check anything. tvars = params @@ -1310,14 +1115,7 @@ def __getitem__(self, params): repr(self)) else: # Subscripting a regular Generic subclass. - if not self.__parameters__: - raise TypeError("%s is not a generic class" % repr(self)) - alen = len(params) - elen = len(self.__parameters__) - if alen != elen: - raise TypeError( - "Too %s parameters for %s; actual %s, expected %s" % - ("many" if alen > elen else "few", repr(self), alen, elen)) + _check_generic(self, params) tvars = _type_vars(params) args = params return self.__class__(self.__name__, @@ -1344,6 +1142,22 @@ def __instancecheck__(self, instance): Generic = None +def _generic_new(base_cls, cls, *args, **kwds): + # Assure type is erased on instantiation, + # but attempt to store it in __orig_class__ + if cls.__origin__ is None: + return base_cls.__new__(cls) + else: + origin = _gorg(cls) + obj = base_cls.__new__(origin) + try: + obj.__orig_class__ = cls + except AttributeError: + pass + obj.__init__(*args, **kwds) + return obj + + class Generic(object): """Abstract base class for generic types. @@ -1369,17 +1183,159 @@ def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT: __slots__ = () def __new__(cls, *args, **kwds): - if cls.__origin__ is None: - return cls.__next_in_mro__.__new__(cls) + return _generic_new(cls.__next_in_mro__, cls, *args, **kwds) + + +class _TypingEmpty(object): + """Placeholder for () or []. Used by TupleMeta and CallableMeta + to allow empy list/tuple in specific places, without allowing them + to sneak in where prohibited. + """ + + +class _TypingEllipsis(object): + """Ditto for ...""" + + +class TupleMeta(GenericMeta): + """Metaclass for Tuple""" + + @_tp_cache + def __getitem__(self, parameters): + if self.__origin__ is not None or not _geqv(self, Tuple): + # Normal generic rules apply if this is not the first subscription + # or a subscription of a subclass. + return super(TupleMeta, self).__getitem__(parameters) + if parameters == (): + return super(TupleMeta, self).__getitem__((_TypingEmpty,)) + if not isinstance(parameters, tuple): + parameters = (parameters,) + if len(parameters) == 2 and parameters[1] is Ellipsis: + msg = "Tuple[t, ...]: t must be a type." + p = _type_check(parameters[0], msg) + return super(TupleMeta, self).__getitem__((p, _TypingEllipsis)) + msg = "Tuple[t0, t1, ...]: each t must be a type." + parameters = tuple(_type_check(p, msg) for p in parameters) + return super(TupleMeta, self).__getitem__(parameters) + + def __instancecheck__(self, obj): + if self.__args__ == None: + return isinstance(obj, tuple) + raise TypeError("Parameterized Tuple cannot be used " + "with isinstance().") + + def __subclasscheck__(self, cls): + if self.__args__ == None: + return issubclass(cls, tuple) + raise TypeError("Parameterized Tuple cannot be used " + "with issubclass().") + + +class Tuple(tuple): + """Tuple type; Tuple[X, Y] is the cross-product type of X and Y. + + Example: Tuple[T1, T2] is a tuple of two elements corresponding + to type variables T1 and T2. Tuple[int, float, str] is a tuple + of an int, a float and a string. + + To specify a variable-length tuple of homogeneous type, use Tuple[T, ...]. + """ + + __metaclass__ = TupleMeta + __extra__ = tuple + __slots__ = () + + def __new__(cls, *args, **kwds): + if _geqv(cls, Tuple): + raise TypeError("Type Tuple cannot be instantiated; " + "use tuple() instead") + return _generic_new(tuple, cls, *args, **kwds) + + +class CallableMeta(GenericMeta): + """ Metaclass for Callable.""" + + def __repr__(self): + if self.__origin__ is None: + return super(CallableMeta, self).__repr__() + return self._tree_repr(self._subs_tree()) + + def _tree_repr(self, tree): + if _gorg(self) 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. + arg_list = [] + for arg in tree[1:]: + if arg == (): + arg_list.append('[]') + elif not isinstance(arg, tuple): + arg_list.append(_type_repr(arg)) + else: + arg_list.append(arg[0]._tree_repr(arg)) + if len(arg_list) == 2: + return repr(tree[0]) + '[%s]' % ', '.join(arg_list) + return (repr(tree[0]) + + '[[%s], %s]' % (', '.join(arg_list[:-1]), arg_list[-1])) + + def __getitem__(self, parameters): + """ A thin wrapper around __getitem_inner__ to provide the latter + with hashable arguments to improve speed. + """ + + if self.__origin__ is not None or not _geqv(self, Callable): + return super(CallableMeta, self).__getitem__(parameters) + if not isinstance(parameters, tuple) or len(parameters) != 2: + raise TypeError("Callable must be used as " + "Callable[[arg, ...], result].") + args, result = parameters + if args is Ellipsis: + parameters = (Ellipsis, result) + elif args == []: + parameters = ((), result) else: - origin = _gorg(cls) - obj = cls.__next_in_mro__.__new__(origin) - try: - obj.__orig_class__ = cls - except AttributeError: - pass - obj.__init__(*args, **kwds) - return obj + if not isinstance(args, list): + raise TypeError("Callable[args, result]: args must be a list." + " Got %.100r." % (args,)) + parameters = tuple(args) + (result,) + return self.__getitem_inner__(parameters) + + @_tp_cache + def __getitem_inner__(self, parameters): + args = parameters[:-1] + result = parameters[-1] + msg = "Callable[args, result]: result must be a type." + result = _type_check(result, msg) + if args == (Ellipsis,): + return super(CallableMeta, self).__getitem__((_TypingEllipsis, result)) + if args == ((),): + return super(CallableMeta, self).__getitem__((_TypingEmpty, result)) + msg = "Callable[[arg, ...], result]: each arg must be a type." + args = tuple(_type_check(arg, msg) for arg in args) + parameters = args + (result,) + return super(CallableMeta, self).__getitem__(parameters) + + +class Callable(object): + """Callable type; Callable[[int], str] is a function of (int) -> str. + + The subscription syntax must always be used with exactly two + values: the argument list and the return type. The argument list + must be a list of types; the return type must be a single type. + + There is no syntax to indicate optional or keyword arguments, + such function types are rarely used as callback types. + """ + + __metaclass__ = CallableMeta + __extra__ = collections_abc.Callable + __slots__ = () + + def __new__(cls, *args, **kwds): + if _geqv(cls, Callable): + raise TypeError("Type Callable cannot be instantiated; " + "use a non-abstract subclass instead") + return _generic_new(cls.__next_in_mro__, cls, *args, **kwds) def cast(typ, val): @@ -1550,6 +1506,7 @@ def _get_protocol_attrs(self): attr != '__origin__' and attr != '__orig_bases__' and attr != '__extra__' and + attr != '__tree_hash__' and attr != '__module__'): attrs.add(attr) @@ -1694,7 +1651,7 @@ def __new__(cls, *args, **kwds): if _geqv(cls, List): raise TypeError("Type List cannot be instantiated; " "use list() instead") - return list.__new__(cls, *args, **kwds) + return _generic_new(list, cls, *args, **kwds) class Set(set, MutableSet[T]): @@ -1705,7 +1662,7 @@ def __new__(cls, *args, **kwds): if _geqv(cls, Set): raise TypeError("Type Set cannot be instantiated; " "use set() instead") - return set.__new__(cls, *args, **kwds) + return _generic_new(set, cls, *args, **kwds) class FrozenSet(frozenset, AbstractSet[T_co]): @@ -1716,7 +1673,7 @@ def __new__(cls, *args, **kwds): if _geqv(cls, FrozenSet): raise TypeError("Type FrozenSet cannot be instantiated; " "use frozenset() instead") - return frozenset.__new__(cls, *args, **kwds) + return _generic_new(frozenset, cls, *args, **kwds) class MappingView(Sized, Iterable[T_co]): @@ -1749,7 +1706,7 @@ def __new__(cls, *args, **kwds): if _geqv(cls, Dict): raise TypeError("Type Dict cannot be instantiated; " "use dict() instead") - return dict.__new__(cls, *args, **kwds) + return _generic_new(dict, cls, *args, **kwds) class DefaultDict(collections.defaultdict, MutableMapping[KT, VT]): @@ -1760,7 +1717,7 @@ def __new__(cls, *args, **kwds): if _geqv(cls, DefaultDict): raise TypeError("Type DefaultDict cannot be instantiated; " "use collections.defaultdict() instead") - return collections.defaultdict.__new__(cls, *args, **kwds) + return _generic_new(collections.defaultdict, cls, *args, **kwds) # Determine what base class to use for Generator. @@ -1780,7 +1737,7 @@ def __new__(cls, *args, **kwds): if _geqv(cls, Generator): raise TypeError("Type Generator cannot be instantiated; " "create a subclass instead") - return super(Generator, cls).__new__(cls, *args, **kwds) + return _generic_new(_G_base, cls, *args, **kwds) # Internal type variable used for Type[]. diff --git a/src/typing.py b/src/typing.py index b787d71b..bda4ea59 100644 --- a/src/typing.py +++ b/src/typing.py @@ -1123,7 +1123,7 @@ def __getitem__(self, parameters): return super().__getitem__((_TypingEmpty,)) if not isinstance(parameters, tuple): parameters = (parameters,) - if len(parameters) == 2 and parameters[1] == ...: + if len(parameters) == 2 and parameters[1] is ...: msg = "Tuple[t, ...]: t must be a type." p = _type_check(parameters[0], msg) return super().__getitem__((p, _TypingEllipsis)) From 60303f7204eaa66d5f1ba36211ae9138409225f4 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 25 Oct 2016 23:44:16 +0200 Subject: [PATCH 38/39] Fix final test in PY 2 --- python2/typing.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/python2/typing.py b/python2/typing.py index 118230cd..105ee0b8 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -1023,14 +1023,16 @@ def __new__(cls, name, bases, namespace, or hasattr(self.__subclasshook__, '__name__') and self.__subclasshook__.__name__ == '__extrahook__'): self.__subclasshook__ = _make_subclasshook(self) - if isinstance(extra, abc.ABCMeta): - self._abc_registry = extra._abc_registry if origin and hasattr(origin, '__qualname__'): # Fix for Python 3.2. self.__qualname__ = origin.__qualname__ self.__tree_hash__ = hash(self._subs_tree()) if origin else hash((self.__name__,)) return self + def __init__(self, *args, **kwargs): + if isinstance(self.__extra__, abc.ABCMeta): + self._abc_registry = self.__extra__._abc_registry + def _get_type_vars(self, tvars): if self.__origin__ and self.__parameters__: _get_type_vars(self.__parameters__, tvars) From ea5dd0546f749bbceab7d0b572f84354c1a72acb Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Wed, 26 Oct 2016 12:05:52 +0200 Subject: [PATCH 39/39] Also call super().__init__ in PY 2 Generic --- python2/typing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/python2/typing.py b/python2/typing.py index 105ee0b8..ff635df5 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -1030,6 +1030,7 @@ def __new__(cls, name, bases, namespace, return self def __init__(self, *args, **kwargs): + super(GenericMeta, self).__init__(*args, **kwargs) if isinstance(self.__extra__, abc.ABCMeta): self._abc_registry = self.__extra__._abc_registry