From abefbe4a72654e7e385944848b0158969ee33d2e Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Thu, 24 Mar 2016 16:35:18 -0400 Subject: [PATCH 1/7] Revamp of generic classes. Dict[str, Tuple[S, T]] is now valid. Still need to backport to python2. --- src/test_typing.py | 78 +++++++++++---- src/typing.py | 237 ++++++++++++++++++++++++--------------------- 2 files changed, 187 insertions(+), 128 deletions(-) diff --git a/src/test_typing.py b/src/test_typing.py index bb8ad6cb..32a2895f 100644 --- a/src/test_typing.py +++ b/src/test_typing.py @@ -486,7 +486,7 @@ def get(self, key: XK, default: XV = None) -> XV: ... -class MySimpleMapping(SimpleMapping): +class MySimpleMapping(SimpleMapping[XK, XV]): def __init__(self): self.store = {} @@ -540,6 +540,7 @@ def test_supports_abs(self): assert not issubclass(str, typing.SupportsAbs) def test_supports_round(self): + issubclass(float, typing.SupportsRound) assert issubclass(float, typing.SupportsRound) assert issubclass(int, typing.SupportsRound) assert not issubclass(str, typing.SupportsRound) @@ -557,13 +558,16 @@ class GenericTests(TestCase): def test_basics(self): X = SimpleMapping[str, Any] - Y = SimpleMapping[XK, str] - X[str, str] - Y[str, str] + assert X.__parameters__ == () + with self.assertRaises(TypeError): + X[str] with self.assertRaises(TypeError): - X[int, str] + X[str, str] + Y = SimpleMapping[XK, str] + assert Y.__parameters__ == (XK,) + Y[str] with self.assertRaises(TypeError): - Y[str, bytes] + Y[str, str] def test_init(self): T = TypeVar('T') @@ -575,9 +579,32 @@ def test_init(self): def test_repr(self): self.assertEqual(repr(SimpleMapping), - __name__ + '.' + 'SimpleMapping[~XK, ~XV]') + __name__ + '.' + 'SimpleMapping<~XK, ~XV>') self.assertEqual(repr(MySimpleMapping), - __name__ + '.' + 'MySimpleMapping[~XK, ~XV]') + __name__ + '.' + 'MySimpleMapping<~XK, ~XV>') + + def test_chain_repr(self): + T = TypeVar('T') + S = TypeVar('S') + + class C(Generic[T]): + pass + + X = C[Tuple[S, T]] + assert X == C[Tuple[S, T]] + assert X != C[Tuple[T, S]] + + Y = X[T, int] + assert Y == X[T, int] + assert Y != X[S, int] + assert Y != X[T, str] + + Z = Y[str] + assert Z == Y[str] + assert Z != Y[int] + assert Z != Y[T] + + assert str(Z).endswith('.C<~T>[typing.Tuple[~S, ~T]]<~S, ~T>[~T, int]<~T>[str]') def test_dict(self): T = TypeVar('T') @@ -632,12 +659,12 @@ class C(Generic[T]): assert C.__module__ == __name__ if not PY32: assert C.__qualname__ == 'GenericTests.test_repr_2..C' - assert repr(C).split('.')[-1] == 'C[~T]' + assert repr(C).split('.')[-1] == 'C<~T>' X = C[int] assert X.__module__ == __name__ if not PY32: assert X.__qualname__ == 'C' - assert repr(X).split('.')[-1] == 'C[int]' + assert repr(X).split('.')[-1] == 'C<~T>[int]' class Y(C[int]): pass @@ -645,7 +672,7 @@ class Y(C[int]): assert Y.__module__ == __name__ if not PY32: assert Y.__qualname__ == 'GenericTests.test_repr_2..Y' - assert repr(Y).split('.')[-1] == 'Y[int]' + assert repr(Y).split('.')[-1] == 'Y' def test_eq_1(self): assert Generic == Generic @@ -673,15 +700,14 @@ class A(Generic[T, VT]): class B(Generic[KT, T]): pass - class C(A, Generic[KT, VT], B): + class C(A[T, VT], Generic[VT, T, KT], B[KT, T]): pass - assert C.__parameters__ == (T, VT, KT) + assert C.__parameters__ == (VT, T, KT) def test_nested(self): - class G(Generic): - pass + G = Generic class Visitor(G[T]): @@ -730,6 +756,24 @@ def foo(x: T): foo(42) + def test_implicit_any(self): + T = TypeVar('T') + + class C(Generic[T]): + pass + + class D(C): + pass + + assert D.__parameters__ == () + + with self.assertRaises(Exception): + D[int] + with self.assertRaises(Exception): + D[Any] + with self.assertRaises(Exception): + D[T] + class VarianceTests(TestCase): @@ -1286,7 +1330,7 @@ def stuff(a: TextIO) -> str: return a.readline() a = stuff.__annotations__['a'] - assert a.__parameters__ == (str,) + assert a.__parameters__ == () def test_binaryio(self): @@ -1294,7 +1338,7 @@ def stuff(a: BinaryIO) -> bytes: return a.readline() a = stuff.__annotations__['a'] - assert a.__parameters__ == (bytes,) + assert a.__parameters__ == () def test_io_submodule(self): from typing.io import IO, TextIO, BinaryIO, __all__, __name__ diff --git a/src/typing.py b/src/typing.py index d11bee7a..cc9028ab 100644 --- a/src/typing.py +++ b/src/typing.py @@ -1,6 +1,11 @@ # TODO nits: # Get rid of asserts that are the caller's fault. # Docstrings (e.g. ABCs). +# Rewrite generics so that: +# - class C(Iterable) means the same as class C(Iterable[Any]) +# - class C(Mapping[str, T]) has one parameter (just T), not two +# - class C(Mapping[str, Tuple[int, T]]) has one parameter (T) +# - more complex cases are disambiguated by inheriting from Generic[...] import abc from abc import abstractmethod, abstractproperty @@ -120,6 +125,9 @@ def _eval_type(self, globalns, localns): def _has_type_var(self): return False + def _get_type_vars(self, tvars): + pass + def __repr__(self): return '%s.%s' % (self.__module__, _qualname(self)) @@ -271,8 +279,16 @@ def __subclasscheck__(self, cls): return issubclass(cls, self.impl_type) -def _has_type_var(t): - return t is not None and isinstance(t, TypingMeta) and t._has_type_var() +def _get_type_vars(types, tvars): + for t in types: + if isinstance(t, TypingMeta): + t._get_type_vars(tvars) + + +def _type_vars(types): + tvars = [] + _get_type_vars(types, tvars) + return tuple(tvars) def _eval_type(t, globalns, localns): @@ -376,7 +392,7 @@ def longest(x: A, y: A) -> A: At runtime, isinstance(x, T) will raise TypeError. However, issubclass(C, T) is true for any class C, and issubclass(str, A) and issubclass(bytes, A) are true, and issubclass(int, A) is - false. + false. (TODO: Why is this needed? This may change. See #136.) Type variables may be marked covariant or contravariant by passing covariant=True or contravariant=True. See PEP 484 for more @@ -410,8 +426,9 @@ def __new__(cls, name, *constraints, bound=None, self.__bound__ = None return self - def _has_type_var(self): - return True + def _get_type_vars(self, tvars): + if self not in tvars: + tvars.append(self) def __repr__(self): if self.__covariant__: @@ -448,7 +465,6 @@ def __subclasscheck__(self, cls): T_contra = TypeVar('T_contra', contravariant=True) # Ditto contravariant. # A useful type variable with constraints. This represents string types. -# TODO: What about bytearray, memoryview? AnyStr = TypeVar('AnyStr', bytes, str) @@ -514,12 +530,9 @@ def _eval_type(self, globalns, localns): return self.__class__(self.__name__, self.__bases__, {}, p, _root=True) - def _has_type_var(self): + def _get_type_vars(self, tvars): if self.__union_params__: - for t in self.__union_params__: - if _has_type_var(t): - return True - return False + _get_type_vars(self.__union_params__, tvars) def __repr__(self): r = super().__repr__() @@ -656,12 +669,9 @@ def __new__(cls, name, bases, namespace, parameters=None, self.__tuple_use_ellipsis__ = use_ellipsis return self - def _has_type_var(self): + def _get_type_vars(self, tvars): if self.__tuple_params__: - for t in self.__tuple_params__: - if _has_type_var(t): - return True - return False + _get_type_vars(self.__tuple_params__, tvars) def _eval_type(self, globalns, localns): tp = self.__tuple_params__ @@ -769,12 +779,9 @@ def __new__(cls, name, bases, namespace, _root=False, self.__result__ = result return self - def _has_type_var(self): + def _get_type_vars(self, tvars): if self.__args__: - for t in self.__args__: - if _has_type_var(t): - return True - return _has_type_var(self.__result__) + _get_type_vars(self.__args__, tvars) def _eval_type(self, globalns, localns): if self.__args__ is None and self.__result__ is None: @@ -881,73 +888,83 @@ def _geqv(a, b): class GenericMeta(TypingMeta, abc.ABCMeta): """Metaclass for generic types.""" - # TODO: Constrain more how Generic is used; only a few - # standard patterns should be allowed. - - # TODO: Use a more precise rule than matching __name__ to decide - # whether two classes are the same. Also, save the formal - # parameters. (These things are related! A solution lies in - # using origin.) - __extra__ = None def __new__(cls, name, bases, namespace, - parameters=None, origin=None, extra=None): - if parameters is None: - # Extract parameters from direct base classes. Only - # direct bases are considered and only those that are - # themselves generic, and parameterized with type - # variables. Don't use bases like Any, Union, Tuple, - # Callable or type variables. - params = None - for base in bases: - if isinstance(base, TypingMeta): - if not isinstance(base, GenericMeta): - raise TypeError( - "You cannot inherit from magic class %s" % - repr(base)) - if base.__parameters__ is None: - continue # The base is unparameterized. - for bp in base.__parameters__: - if _has_type_var(bp) and not isinstance(bp, TypeVar): - raise TypeError( - "Cannot inherit from a generic class " - "parameterized with " - "non-type-variable %s" % bp) - if params is None: - params = [] - if bp not in params: - params.append(bp) - if params is not None: - parameters = tuple(params) + tvars=None, args=None, origin=None, extra=None): self = super().__new__(cls, name, bases, namespace, _root=True) - self.__parameters__ = parameters + + if tvars is not None: + # Called from __getitem__() below. + assert origin is not None + assert all(isinstance(t, TypeVar) for t in tvars), tvars + else: + # Called from class statement. + assert tvars is None, tvars + assert args is None, args + assert origin is None, origin + + # Get the full set of tvars from the bases. + tvars = _type_vars(bases) + # Look for Generic[T1, ..., Tn]. + # If found, tvars must be a subset of it. + # If not found, tvars is it. + # Also check for and reject plain Generic, + # and reject multiple Generic[...]. + gvars = None + for base in bases: + if base is Generic: + raise TypeError("Cannot inherit from plain Generic") + if isinstance(base, GenericMeta) and base.__origin__ is Generic: + if gvars is not None: + raise TypeError("Cannot inherit from Generic[...] multiple types.") + gvars = base.__parameters__ + if gvars is None: + gvars = tvars + else: + tvarset = set(tvars) + gvarset = set(gvars) + if not tvarset <= gvarset: + raise TypeError("Some type variables (%s) are not listed in Generic[%s]" % + (", ".join(str(t) for t in tvars if t not in gvarset), + ", ".join(str(g) for g in gvars))) + tvars = gvars + + self.__parameters__ = tvars + self.__args__ = args + self.__origin__ = origin if extra is not None: self.__extra__ = extra # Else __extra__ is inherited, eventually from the # (meta-)class default above. - self.__origin__ = origin return self - def _has_type_var(self): - if self.__parameters__: - for t in self.__parameters__: - if _has_type_var(t): - return True - return False + def _get_type_vars(self, tvars): + if self.__origin__ and self.__parameters__: + _get_type_vars(self.__parameters__, tvars) def __repr__(self): - r = super().__repr__() - if self.__parameters__ is not None: + if self.__origin__ is not None: + r = repr(self.__origin__) + else: + r = super().__repr__() + if self.__args__: r += '[%s]' % ( + ', '.join(_type_repr(p) for p in self.__args__)) + if self.__parameters__: + r += '<%s>' % ( ', '.join(_type_repr(p) for p in self.__parameters__)) return r def __eq__(self, other): if not isinstance(other, GenericMeta): return NotImplemented - return (_geqv(self, other) and - self.__parameters__ == other.__parameters__) + if self.__origin__ is not None: + return (self.__origin__ is other.__origin__ and + self.__args__ == other.__args__ and + self.__parameters__ == other.__parameters__) + else: + return self is other def __hash__(self): return hash((self.__name__, self.__parameters__)) @@ -956,37 +973,41 @@ def __getitem__(self, params): if not isinstance(params, tuple): params = (params,) if not params: - raise TypeError("Cannot have empty parameter list") + raise TypeError("Parameter list to %s[...] cannot be empty" % _qualname(self)) msg = "Parameters to generic types must be types." params = tuple(_type_check(p, msg) for p in params) - if self.__parameters__ is None: - for p in params: - if not isinstance(p, TypeVar): - raise TypeError("Initial parameters must be " - "type variables; got %s" % p) + if self is Generic: + # Generic can only be subscripted with unique type variables. + if not all(isinstance(p, TypeVar) for p in params): + raise TypeError("Parameters to Generic[...] must all be type variables") if len(set(params)) != len(params): - raise TypeError( - "All type variables in Generic[...] must be distinct.") + raise TypeError("Parameters to Generic[...] must all be unique") + tvars = params + args = None + elif self is _Protocol: + # _Protocol is internal, don't check anything. + tvars = params + args = None + elif self.__origin__ in (Generic, _Protocol): + # Can't subscript Generic[...] or _Protocol[...]. + raise TypeError("Cannot subscript already-subscripted %s" % repr(self)) else: - if len(params) != len(self.__parameters__): - raise TypeError("Cannot change parameter count from %d to %d" % - (len(self.__parameters__), len(params))) - for new, old in zip(params, self.__parameters__): - if isinstance(old, TypeVar): - if not old.__constraints__: - # Substituting for an unconstrained TypeVar is OK. - continue - if issubclass(new, Union[old.__constraints__]): - # Specializing a constrained type variable is OK. - continue - if not issubclass(new, old): - raise TypeError( - "Cannot substitute %s for %s in %s" % - (_type_repr(new), _type_repr(old), self)) - - return self.__class__(self.__name__, (self,) + self.__bases__, + # 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)) + tvars = _type_vars(params) + args = params + return self.__class__(self.__name__, + (self,) + self.__bases__, dict(self.__dict__), - parameters=params, + tvars=tvars, + args=args, origin=self, extra=self.__extra__) @@ -1006,10 +1027,10 @@ def __subclasscheck__(self, cls): # C[X] is a subclass of C[Y] iff X is a subclass of Y. origin = self.__origin__ if origin is not None and origin is cls.__origin__: - assert len(self.__parameters__) == len(origin.__parameters__) - assert len(cls.__parameters__) == len(origin.__parameters__) - for p_self, p_cls, p_origin in zip(self.__parameters__, - cls.__parameters__, + assert len(self.__args__) == len(origin.__parameters__) + assert len(cls.__args__) == len(origin.__parameters__) + for p_self, p_cls, p_origin in zip(self.__args__, + cls.__args__, origin.__parameters__): if isinstance(p_origin, TypeVar): if p_origin.__covariant__: @@ -1053,18 +1074,11 @@ def __getitem__(self, key: KT) -> VT: This class can then be used as follows:: - def lookup_name(mapping: Mapping, key: KT, default: VT) -> VT: + def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT: try: return mapping[key] except KeyError: return default - - For clarity the type variables may be redefined, e.g.:: - - X = TypeVar('X') - Y = TypeVar('Y') - def lookup_name(mapping: Mapping[X, Y], key: X, default: Y) -> Y: - # Same body as above. """ __slots__ = () @@ -1148,7 +1162,6 @@ def get_type_hints(obj, globalns=None, localns=None): return hints -# TODO: Also support this as a class decorator. def no_type_check(arg): """Decorator to indicate that annotations are not type hints. @@ -1269,6 +1282,7 @@ def _get_protocol_attrs(self): attr != '__abstractmethods__' and attr != '_is_protocol' and attr != '__dict__' and + attr != '__args__' and attr != '__slots__' and attr != '_get_protocol_attrs' and attr != '__parameters__' and @@ -1402,7 +1416,7 @@ class MutableSet(AbstractSet[T], extra=collections_abc.MutableSet): # NOTE: Only the value type is covariant. -class Mapping(Sized, Iterable[KT], Container[KT], Generic[VT_co], +class Mapping(Sized, Iterable[KT], Container[KT], Generic[KT, VT_co], extra=collections_abc.Mapping): pass @@ -1478,8 +1492,9 @@ class KeysView(MappingView[KT], AbstractSet[KT], pass -# TODO: Enable Set[Tuple[KT, VT_co]] instead of Generic[KT, VT_co]. -class ItemsView(MappingView, Generic[KT, VT_co], +class ItemsView(MappingView[Tuple[KT, VT_co]], + Set[Tuple[KT, VT_co]], + Generic[KT, VT_co], extra=collections_abc.ItemsView): pass From 39324fa8f56d7817c405b650cf960b486f59a6e8 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Thu, 24 Mar 2016 17:00:27 -0400 Subject: [PATCH 2/7] Backported revamp of generic classes to Python 2. --- python2/test_typing.py | 72 ++++++++++--- python2/typing.py | 235 +++++++++++++++++++++-------------------- 2 files changed, 180 insertions(+), 127 deletions(-) diff --git a/python2/test_typing.py b/python2/test_typing.py index 62d6ddc1..a0e5655e 100644 --- a/python2/test_typing.py +++ b/python2/test_typing.py @@ -478,7 +478,7 @@ def get(self, key, default=None): pass -class MySimpleMapping(SimpleMapping): +class MySimpleMapping(SimpleMapping[XK, XV]): def __init__(self): self.store = {} @@ -533,14 +533,17 @@ def test_protocol_instance_type_error(self): class GenericTests(TestCase): def test_basics(self): - X = SimpleMapping[unicode, Any] - Y = SimpleMapping[XK, unicode] - X[unicode, unicode] - Y[unicode, unicode] + X = SimpleMapping[str, Any] + assert X.__parameters__ == () + with self.assertRaises(TypeError): + X[unicode] with self.assertRaises(TypeError): - X[int, unicode] + X[unicode, unicode] + Y = SimpleMapping[XK, unicode] + assert Y.__parameters__ == (XK,) + Y[unicode] with self.assertRaises(TypeError): - Y[unicode, bytes] + Y[unicode, unicode] def test_init(self): T = TypeVar('T') @@ -552,9 +555,32 @@ def test_init(self): def test_repr(self): self.assertEqual(repr(SimpleMapping), - __name__ + '.' + 'SimpleMapping[~XK, ~XV]') + __name__ + '.' + 'SimpleMapping<~XK, ~XV>') self.assertEqual(repr(MySimpleMapping), - __name__ + '.' + 'MySimpleMapping[~XK, ~XV]') + __name__ + '.' + 'MySimpleMapping<~XK, ~XV>') + + def test_chain_repr(self): + T = TypeVar('T') + S = TypeVar('S') + + class C(Generic[T]): + pass + + X = C[Tuple[S, T]] + assert X == C[Tuple[S, T]] + assert X != C[Tuple[T, S]] + + Y = X[T, int] + assert Y == X[T, int] + assert Y != X[S, int] + assert Y != X[T, str] + + Z = Y[str] + assert Z == Y[str] + assert Z != Y[int] + assert Z != Y[T] + + assert str(Z).endswith('.C<~T>[typing.Tuple[~S, ~T]]<~S, ~T>[~T, int]<~T>[str]') def test_dict(self): T = TypeVar('T') @@ -609,12 +635,12 @@ class C(Generic[T]): assert C.__module__ == __name__ if not PY32: assert C.__qualname__ == 'GenericTests.test_repr_2..C' - assert repr(C).split('.')[-1] == 'C[~T]' + assert repr(C).split('.')[-1] == 'C<~T>' X = C[int] assert X.__module__ == __name__ if not PY32: assert X.__qualname__ == 'C' - assert repr(X).split('.')[-1] == 'C[int]' + assert repr(X).split('.')[-1] == 'C<~T>[int]' class Y(C[int]): pass @@ -622,7 +648,7 @@ class Y(C[int]): assert Y.__module__ == __name__ if not PY32: assert Y.__qualname__ == 'GenericTests.test_repr_2..Y' - assert repr(Y).split('.')[-1] == 'Y[int]' + assert repr(Y).split('.')[-1] == 'Y' def test_eq_1(self): assert Generic == Generic @@ -650,10 +676,10 @@ class A(Generic[T, VT]): class B(Generic[KT, T]): pass - class C(A, Generic[KT, VT], B): + class C(A[T, VT], Generic[VT, T, KT], B[KT, T]): pass - assert C.__parameters__ == (T, VT, KT) + assert C.__parameters__ == (VT, T, KT) def test_type_erasure(self): T = TypeVar('T') @@ -676,6 +702,24 @@ def foo(x): foo(42) + def test_implicit_any(self): + T = TypeVar('T') + + class C(Generic[T]): + pass + + class D(C): + pass + + assert D.__parameters__ == () + + with self.assertRaises(Exception): + D[int] + with self.assertRaises(Exception): + D[Any] + with self.assertRaises(Exception): + D[T] + class VarianceTests(TestCase): diff --git a/python2/typing.py b/python2/typing.py index a4ae364a..1b6f59e6 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -113,8 +113,8 @@ def _eval_type(self, globalns, localns): """ return self - def _has_type_var(self): - return False + def _get_type_vars(self, tvars): + pass def __repr__(self): return '%s.%s' % (self.__module__, _qualname(self)) @@ -267,8 +267,16 @@ def __subclasscheck__(self, cls): return issubclass(cls, self.impl_type) -def _has_type_var(t): - return t is not None and isinstance(t, TypingMeta) and t._has_type_var() +def _get_type_vars(types, tvars): + for t in types: + if isinstance(t, TypingMeta): + t._get_type_vars(tvars) + + +def _type_vars(types): + tvars = [] + _get_type_vars(types, tvars) + return tuple(tvars) def _eval_type(t, globalns, localns): @@ -380,7 +388,7 @@ def longest(x: A, y: A) -> A: At runtime, isinstance(x, T) will raise TypeError. However, issubclass(C, T) is true for any class C, and issubclass(str, A) and issubclass(bytes, A) are true, and issubclass(int, A) is - false. + false. (TODO: Why is this needed? This may change. See #136.) Type variables may be marked covariant or contravariant by passing covariant=True or contravariant=True. See PEP 484 for more @@ -418,8 +426,9 @@ def __new__(cls, name, *constraints, **kwargs): self.__bound__ = None return self - def _has_type_var(self): - return True + def _get_type_vars(self, tvars): + if self not in tvars: + tvars.append(self) def __repr__(self): if self.__covariant__: @@ -456,7 +465,6 @@ def __subclasscheck__(self, cls): T_contra = TypeVar('T_contra', contravariant=True) # Ditto contravariant. # A useful type variable with constraints. This represents string types. -# TODO: What about bytearray, memoryview? AnyStr = TypeVar('AnyStr', bytes, unicode) @@ -523,12 +531,9 @@ def _eval_type(self, globalns, localns): return self.__class__(self.__name__, self.__bases__, {}, p) - def _has_type_var(self): + def _get_type_vars(self, tvars): if self.__union_params__: - for t in self.__union_params__: - if _has_type_var(t): - return True - return False + _get_type_vars(self.__union_params__, tvars) def __repr__(self): r = super(UnionMeta, self).__repr__() @@ -670,12 +675,9 @@ def __new__(cls, name, bases, namespace, parameters=None, self.__tuple_use_ellipsis__ = use_ellipsis return self - def _has_type_var(self): + def _get_type_vars(self, tvars): if self.__tuple_params__: - for t in self.__tuple_params__: - if _has_type_var(t): - return True - return False + _get_type_vars(self.__tuple_params__, tvars) def _eval_type(self, globalns, localns): tp = self.__tuple_params__ @@ -785,12 +787,9 @@ def __new__(cls, name, bases, namespace, self.__result__ = result return self - def _has_type_var(self): + def _get_type_vars(self, tvars): if self.__args__: - for t in self.__args__: - if _has_type_var(t): - return True - return _has_type_var(self.__result__) + _get_type_vars(self.__args__, tvars) def _eval_type(self, globalns, localns): if self.__args__ is None and self.__result__ is None: @@ -898,73 +897,85 @@ def _geqv(a, b): class GenericMeta(TypingMeta, abc.ABCMeta): """Metaclass for generic types.""" - # TODO: Constrain more how Generic is used; only a few - # standard patterns should be allowed. - - # TODO: Use a more precise rule than matching __name__ to decide - # whether two classes are the same. Also, save the formal - # parameters. (These things are related! A solution lies in - # using origin.) - __extra__ = None def __new__(cls, name, bases, namespace, - parameters=None, origin=None, extra=None): - if parameters is None: - # Extract parameters from direct base classes. Only - # direct bases are considered and only those that are - # themselves generic, and parameterized with type - # variables. Don't use bases like Any, Union, Tuple, - # Callable or type variables. - params = None - for base in bases: - if isinstance(base, TypingMeta): - if not isinstance(base, GenericMeta): - raise TypeError( - "You cannot inherit from magic class %s" % - repr(base)) - if base.__parameters__ is None: - continue # The base is unparameterized. - for bp in base.__parameters__: - if _has_type_var(bp) and not isinstance(bp, TypeVar): - raise TypeError( - "Cannot inherit from a generic class " - "parameterized with " - "non-type-variable %s" % bp) - if params is None: - params = [] - if bp not in params: - params.append(bp) - if params is not None: - parameters = tuple(params) + tvars=None, args=None, origin=None, extra=None): self = super(GenericMeta, cls).__new__(cls, name, bases, namespace) - self.__parameters__ = parameters + + if tvars is not None: + # Called from __getitem__() below. + assert origin is not None + assert all(isinstance(t, TypeVar) for t in tvars), tvars + else: + # Called from class statement. + assert tvars is None, tvars + assert args is None, args + assert origin is None, origin + + # Get the full set of tvars from the bases. + tvars = _type_vars(bases) + # Look for Generic[T1, ..., Tn]. + # If found, tvars must be a subset of it. + # If not found, tvars is it. + # Also check for and reject plain Generic, + # and reject multiple Generic[...]. + gvars = None + for base in bases: + if base is object: + continue # Avoid checking for Generic in its own definition. + if base is Generic: + raise TypeError("Cannot inherit from plain Generic") + if isinstance(base, GenericMeta) and base.__origin__ is Generic: + if gvars is not None: + raise TypeError("Cannot inherit from Generic[...] multiple types.") + gvars = base.__parameters__ + if gvars is None: + gvars = tvars + else: + tvarset = set(tvars) + gvarset = set(gvars) + if not tvarset <= gvarset: + raise TypeError("Some type variables (%s) are not listed in Generic[%s]" % + (", ".join(str(t) for t in tvars if t not in gvarset), + ", ".join(str(g) for g in gvars))) + tvars = gvars + + self.__parameters__ = tvars + self.__args__ = args + self.__origin__ = origin if extra is not None: self.__extra__ = extra # Else __extra__ is inherited, eventually from the # (meta-)class default above. - self.__origin__ = origin return self - def _has_type_var(self): - if self.__parameters__: - for t in self.__parameters__: - if _has_type_var(t): - return True - return False + def _get_type_vars(self, tvars): + if self.__origin__ and self.__parameters__: + _get_type_vars(self.__parameters__, tvars) def __repr__(self): - r = super(GenericMeta, self).__repr__() - if self.__parameters__ is not None: + if self.__origin__ is not None: + r = repr(self.__origin__) + else: + r = super(GenericMeta, self).__repr__() + if self.__args__: r += '[%s]' % ( + ', '.join(_type_repr(p) for p in self.__args__)) + if self.__parameters__: + r += '<%s>' % ( ', '.join(_type_repr(p) for p in self.__parameters__)) return r def __eq__(self, other): if not isinstance(other, GenericMeta): return NotImplemented - return (_geqv(self, other) and - self.__parameters__ == other.__parameters__) + if self.__origin__ is not None: + return (self.__origin__ is other.__origin__ and + self.__args__ == other.__args__ and + self.__parameters__ == other.__parameters__) + else: + return self is other def __hash__(self): return hash((self.__name__, self.__parameters__)) @@ -973,37 +984,41 @@ def __getitem__(self, params): if not isinstance(params, tuple): params = (params,) if not params: - raise TypeError("Cannot have empty parameter list") + raise TypeError("Parameter list to %s[...] cannot be empty" % _qualname(self)) msg = "Parameters to generic types must be types." params = tuple(_type_check(p, msg) for p in params) - if self.__parameters__ is None: - for p in params: - if not isinstance(p, TypeVar): - raise TypeError("Initial parameters must be " - "type variables; got %s" % p) + if self is Generic: + # Generic can only be subscripted with unique type variables. + if not all(isinstance(p, TypeVar) for p in params): + raise TypeError("Parameters to Generic[...] must all be type variables") if len(set(params)) != len(params): - raise TypeError( - "All type variables in Generic[...] must be distinct.") + raise TypeError("Parameters to Generic[...] must all be unique") + tvars = params + args = None + elif self is _Protocol: + # _Protocol is internal, don't check anything. + tvars = params + args = None + elif self.__origin__ in (Generic, _Protocol): + # Can't subscript Generic[...] or _Protocol[...]. + raise TypeError("Cannot subscript already-subscripted %s" % repr(self)) else: - if len(params) != len(self.__parameters__): - raise TypeError("Cannot change parameter count from %d to %d" % - (len(self.__parameters__), len(params))) - for new, old in zip(params, self.__parameters__): - if isinstance(old, TypeVar): - if not old.__constraints__: - # Substituting for an unconstrained TypeVar is OK. - continue - if issubclass(new, Union[old.__constraints__]): - # Specializing a constrained type variable is OK. - continue - if not issubclass(new, old): - raise TypeError( - "Cannot substitute %s for %s in %s" % - (_type_repr(new), _type_repr(old), self)) - - return self.__class__(self.__name__, (self,) + self.__bases__, + # 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)) + tvars = _type_vars(params) + args = params + return self.__class__(self.__name__, + (self,) + self.__bases__, dict(self.__dict__), - parameters=params, + tvars=tvars, + args=args, origin=self, extra=self.__extra__) @@ -1023,10 +1038,10 @@ def __subclasscheck__(self, cls): # C[X] is a subclass of C[Y] iff X is a subclass of Y. origin = self.__origin__ if origin is not None and origin is cls.__origin__: - assert len(self.__parameters__) == len(origin.__parameters__) - assert len(cls.__parameters__) == len(origin.__parameters__) - for p_self, p_cls, p_origin in zip(self.__parameters__, - cls.__parameters__, + assert len(self.__args__) == len(origin.__parameters__) + assert len(cls.__args__) == len(origin.__parameters__) + for p_self, p_cls, p_origin in zip(self.__args__, + cls.__args__, origin.__parameters__): if isinstance(p_origin, TypeVar): if p_origin.__covariant__: @@ -1070,18 +1085,11 @@ def __getitem__(self, key: KT) -> VT: This class can then be used as follows:: - def lookup_name(mapping: Mapping, key: KT, default: VT) -> VT: + def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT: try: return mapping[key] except KeyError: return default - - For clarity the type variables may be redefined, e.g.:: - - X = TypeVar('X') - Y = TypeVar('Y') - def lookup_name(mapping: Mapping[X, Y], key: X, default: Y) -> Y: - # Same body as above. """ __metaclass__ = GenericMeta @@ -1166,7 +1174,6 @@ def get_type_hints(obj, globalns=None, localns=None): return hints -# TODO: Also support this as a class decorator. def no_type_check(arg): """Decorator to indicate that annotations are not type hints. @@ -1287,6 +1294,7 @@ def _get_protocol_attrs(self): attr != '__abstractmethods__' and attr != '_is_protocol' and attr != '__dict__' and + attr != '__args__' and attr != '__slots__' and attr != '_get_protocol_attrs' and attr != '__parameters__' and @@ -1387,7 +1395,7 @@ class MutableSet(AbstractSet[T]): # NOTE: Only the value type is covariant. -class Mapping(Sized, Iterable[KT], Container[KT], Generic[VT_co]): +class Mapping(Sized, Iterable[KT], Container[KT], Generic[KT, VT_co]): __extra__ = collections_abc.Mapping @@ -1461,8 +1469,9 @@ class KeysView(MappingView[KT], AbstractSet[KT]): __extra__ = collections_abc.KeysView -# TODO: Enable Set[Tuple[KT, VT_co]] instead of Generic[KT, VT_co]. -class ItemsView(MappingView, Generic[KT, VT_co]): +class ItemsView(MappingView[Tuple[KT, VT_co]], + Set[Tuple[KT, VT_co]], + Generic[KT, VT_co]): __extra__ = collections_abc.ItemsView From 31b0e0047e454a644d75ee26fa2f7128fffa4836 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Thu, 24 Mar 2016 17:22:00 -0400 Subject: [PATCH 3/7] Make flake8 happy. --- python2/test_typing.py | 5 +++- python2/typing.py | 66 +++++++++++++++++++++------------------- src/test_typing.py | 5 +++- src/typing.py | 68 ++++++++++++++++++++---------------------- tox.ini | 5 +++- 5 files changed, 80 insertions(+), 69 deletions(-) diff --git a/python2/test_typing.py b/python2/test_typing.py index a0e5655e..0ab679d1 100644 --- a/python2/test_typing.py +++ b/python2/test_typing.py @@ -132,6 +132,7 @@ def test_basic_constrained(self): def test_constrained_error(self): with self.assertRaises(TypeError): X = TypeVar('X', int) + X def test_union_unique(self): X = TypeVar('X') @@ -316,6 +317,7 @@ def test_union_instance_type_error(self): def test_union_str_pattern(self): # Shouldn't crash; see http://bugs.python.org/issue25390 A = Union[str, Pattern] + A class TypeVarUnionTests(TestCase): @@ -580,7 +582,8 @@ class C(Generic[T]): assert Z != Y[int] assert Z != Y[T] - assert str(Z).endswith('.C<~T>[typing.Tuple[~S, ~T]]<~S, ~T>[~T, int]<~T>[str]') + assert str(Z).endswith( + '.C<~T>[typing.Tuple[~S, ~T]]<~S, ~T>[~T, int]<~T>[str]') def test_dict(self): T = TypeVar('T') diff --git a/python2/typing.py b/python2/typing.py index 1b6f59e6..3320b486 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -1,7 +1,3 @@ -# TODO nits: -# Get rid of asserts that are the caller's fault. -# Docstrings (e.g. ABCs). - from __future__ import absolute_import, unicode_literals import abc @@ -210,8 +206,8 @@ def __new__(cls, *args, **kwds): someone tries to subclass a type alias (not a good idea). """ if (len(args) == 3 and - isinstance(args[0], basestring) and - isinstance(args[1], tuple)): + isinstance(args[0], basestring) and + isinstance(args[1], tuple)): # Close enough. raise TypeError("A type alias cannot be subclassed") return object.__new__(cls) @@ -732,7 +728,8 @@ def __subclasscheck__(self, cls): if cls is Any: return True if not isinstance(cls, type): - return super(TupleMeta, self).__subclasscheck__(cls) # To TypeError. + # To TypeError. + return super(TupleMeta, self).__subclasscheck__(cls) if issubclass(cls, tuple): return True # Special case. if not isinstance(cls, TupleMeta): @@ -923,12 +920,15 @@ def __new__(cls, name, bases, namespace, gvars = None for base in bases: if base is object: - continue # Avoid checking for Generic in its own definition. + # Avoid checking for Generic in its own definition. + continue if base is Generic: raise TypeError("Cannot inherit from plain Generic") - if isinstance(base, GenericMeta) and base.__origin__ is Generic: + if (isinstance(base, GenericMeta) and + base.__origin__ is Generic): if gvars is not None: - raise TypeError("Cannot inherit from Generic[...] multiple types.") + raise TypeError( + "Cannot inherit from Generic[...] multiple types.") gvars = base.__parameters__ if gvars is None: gvars = tvars @@ -936,9 +936,11 @@ def __new__(cls, name, bases, namespace, tvarset = set(tvars) gvarset = set(gvars) if not tvarset <= gvarset: - raise TypeError("Some type variables (%s) are not listed in Generic[%s]" % - (", ".join(str(t) for t in tvars if t not in gvarset), - ", ".join(str(g) for g in gvars))) + raise TypeError( + "Some type variables (%s) " + "are not listed in Generic[%s]" % + (", ".join(str(t) for t in tvars if t not in gvarset), + ", ".join(str(g) for g in gvars))) tvars = gvars self.__parameters__ = tvars @@ -984,15 +986,18 @@ def __getitem__(self, params): if not isinstance(params, tuple): params = (params,) if not params: - raise TypeError("Parameter list to %s[...] cannot be empty" % _qualname(self)) + raise TypeError( + "Parameter list to %s[...] cannot be empty" % _qualname(self)) msg = "Parameters to generic types must be types." params = tuple(_type_check(p, msg) for p in params) if self is Generic: # Generic can only be subscripted with unique type variables. if not all(isinstance(p, TypeVar) for p in params): - raise TypeError("Parameters to Generic[...] must all be type variables") + raise TypeError( + "Parameters to Generic[...] must all be type variables") if len(set(params)) != len(params): - raise TypeError("Parameters to Generic[...] must all be unique") + raise TypeError( + "Parameters to Generic[...] must all be unique") tvars = params args = None elif self is _Protocol: @@ -1001,7 +1006,8 @@ def __getitem__(self, params): args = None elif self.__origin__ in (Generic, _Protocol): # Can't subscript Generic[...] or _Protocol[...]. - raise TypeError("Cannot subscript already-subscripted %s" % repr(self)) + raise TypeError("Cannot subscript already-subscripted %s" % + repr(self)) else: # Subscripting a regular Generic subclass. if not self.__parameters__: @@ -1009,9 +1015,9 @@ def __getitem__(self, params): 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)) + raise TypeError( + "Too %s parameters for %s; actual %s, expected %s" % + ("many" if alen > elen else "few", repr(self), alen, elen)) tvars = _type_vars(params) args = params return self.__class__(self.__name__, @@ -1119,9 +1125,7 @@ def _get_defaults(func): """Internal helper to extract the default arguments, by name.""" code = func.__code__ pos_count = code.co_argcount - kw_count = code.co_kwonlyargcount arg_names = code.co_varnames - kwarg_names = arg_names[pos_count:pos_count + kw_count] arg_names = arg_names[:pos_count] defaults = func.__defaults__ or () kwdefaults = func.__kwdefaults__ @@ -1291,15 +1295,15 @@ def _get_protocol_attrs(self): break else: if (not attr.startswith('_abc_') and - attr != '__abstractmethods__' and - attr != '_is_protocol' and - attr != '__dict__' and - attr != '__args__' and - attr != '__slots__' and - attr != '_get_protocol_attrs' and - attr != '__parameters__' and - attr != '__origin__' and - attr != '__module__'): + attr != '__abstractmethods__' and + attr != '_is_protocol' and + attr != '__dict__' and + attr != '__args__' and + attr != '__slots__' and + attr != '_get_protocol_attrs' and + attr != '__parameters__' and + attr != '__origin__' and + attr != '__module__'): attrs.add(attr) return attrs diff --git a/src/test_typing.py b/src/test_typing.py index 32a2895f..7089334c 100644 --- a/src/test_typing.py +++ b/src/test_typing.py @@ -132,6 +132,7 @@ def test_basic_constrained(self): def test_constrained_error(self): with self.assertRaises(TypeError): X = TypeVar('X', int) + X def test_union_unique(self): X = TypeVar('X') @@ -316,6 +317,7 @@ def test_union_instance_type_error(self): def test_union_str_pattern(self): # Shouldn't crash; see http://bugs.python.org/issue25390 A = Union[str, Pattern] + A class TypeVarUnionTests(TestCase): @@ -604,7 +606,8 @@ class C(Generic[T]): assert Z != Y[int] assert Z != Y[T] - assert str(Z).endswith('.C<~T>[typing.Tuple[~S, ~T]]<~S, ~T>[~T, int]<~T>[str]') + assert str(Z).endswith( + '.C<~T>[typing.Tuple[~S, ~T]]<~S, ~T>[~T, int]<~T>[str]') def test_dict(self): T = TypeVar('T') diff --git a/src/typing.py b/src/typing.py index cc9028ab..65c50287 100644 --- a/src/typing.py +++ b/src/typing.py @@ -1,12 +1,3 @@ -# TODO nits: -# Get rid of asserts that are the caller's fault. -# Docstrings (e.g. ABCs). -# Rewrite generics so that: -# - class C(Iterable) means the same as class C(Iterable[Any]) -# - class C(Mapping[str, T]) has one parameter (just T), not two -# - class C(Mapping[str, Tuple[int, T]]) has one parameter (T) -# - more complex cases are disambiguated by inheriting from Generic[...] - import abc from abc import abstractmethod, abstractproperty import collections @@ -222,8 +213,8 @@ def __new__(cls, *args, **kwds): someone tries to subclass a type alias (not a good idea). """ if (len(args) == 3 and - isinstance(args[0], str) and - isinstance(args[1], tuple)): + isinstance(args[0], str) and + isinstance(args[1], tuple)): # Close enough. raise TypeError("A type alias cannot be subclassed") return object.__new__(cls) @@ -915,9 +906,11 @@ def __new__(cls, name, bases, namespace, for base in bases: if base is Generic: raise TypeError("Cannot inherit from plain Generic") - if isinstance(base, GenericMeta) and base.__origin__ is Generic: + if (isinstance(base, GenericMeta) and + base.__origin__ is Generic): if gvars is not None: - raise TypeError("Cannot inherit from Generic[...] multiple types.") + raise TypeError( + "Cannot inherit from Generic[...] multiple types.") gvars = base.__parameters__ if gvars is None: gvars = tvars @@ -925,9 +918,11 @@ def __new__(cls, name, bases, namespace, tvarset = set(tvars) gvarset = set(gvars) if not tvarset <= gvarset: - raise TypeError("Some type variables (%s) are not listed in Generic[%s]" % - (", ".join(str(t) for t in tvars if t not in gvarset), - ", ".join(str(g) for g in gvars))) + raise TypeError( + "Some type variables (%s) " + "are not listed in Generic[%s]" % + (", ".join(str(t) for t in tvars if t not in gvarset), + ", ".join(str(g) for g in gvars))) tvars = gvars self.__parameters__ = tvars @@ -973,15 +968,18 @@ def __getitem__(self, params): if not isinstance(params, tuple): params = (params,) if not params: - raise TypeError("Parameter list to %s[...] cannot be empty" % _qualname(self)) + raise TypeError( + "Parameter list to %s[...] cannot be empty" % _qualname(self)) msg = "Parameters to generic types must be types." params = tuple(_type_check(p, msg) for p in params) if self is Generic: # Generic can only be subscripted with unique type variables. if not all(isinstance(p, TypeVar) for p in params): - raise TypeError("Parameters to Generic[...] must all be type variables") + raise TypeError( + "Parameters to Generic[...] must all be type variables") if len(set(params)) != len(params): - raise TypeError("Parameters to Generic[...] must all be unique") + raise TypeError( + "Parameters to Generic[...] must all be unique") tvars = params args = None elif self is _Protocol: @@ -990,7 +988,8 @@ def __getitem__(self, params): args = None elif self.__origin__ in (Generic, _Protocol): # Can't subscript Generic[...] or _Protocol[...]. - raise TypeError("Cannot subscript already-subscripted %s" % repr(self)) + raise TypeError("Cannot subscript already-subscripted %s" % + repr(self)) else: # Subscripting a regular Generic subclass. if not self.__parameters__: @@ -998,9 +997,9 @@ def __getitem__(self, params): 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)) + raise TypeError( + "Too %s parameters for %s; actual %s, expected %s" % + ("many" if alen > elen else "few", repr(self), alen, elen)) tvars = _type_vars(params) args = params return self.__class__(self.__name__, @@ -1107,9 +1106,7 @@ def _get_defaults(func): """Internal helper to extract the default arguments, by name.""" code = func.__code__ pos_count = code.co_argcount - kw_count = code.co_kwonlyargcount arg_names = code.co_varnames - kwarg_names = arg_names[pos_count:pos_count + kw_count] arg_names = arg_names[:pos_count] defaults = func.__defaults__ or () kwdefaults = func.__kwdefaults__ @@ -1279,15 +1276,15 @@ def _get_protocol_attrs(self): break else: if (not attr.startswith('_abc_') and - attr != '__abstractmethods__' and - attr != '_is_protocol' and - attr != '__dict__' and - attr != '__args__' and - attr != '__slots__' and - attr != '_get_protocol_attrs' and - attr != '__parameters__' and - attr != '__origin__' and - attr != '__module__'): + attr != '__abstractmethods__' and + attr != '_is_protocol' and + attr != '__dict__' and + attr != '__args__' and + attr != '__slots__' and + attr != '_get_protocol_attrs' and + attr != '__parameters__' and + attr != '__origin__' and + attr != '__module__'): attrs.add(attr) return attrs @@ -1324,7 +1321,8 @@ class Awaitable(Generic[T_co], extra=collections_abc.Awaitable): class AsyncIterable(Generic[T_co], extra=collections_abc.AsyncIterable): __slots__ = () - class AsyncIterator(AsyncIterable[T_co], extra=collections_abc.AsyncIterator): + class AsyncIterator(AsyncIterable[T_co], + extra=collections_abc.AsyncIterator): __slots__ = () else: diff --git a/tox.ini b/tox.ini index 187303aa..079f02c3 100644 --- a/tox.ini +++ b/tox.ini @@ -9,4 +9,7 @@ commands = python -m unittest discover changedir = python2 [pep8] -ignore = E129,E226 +ignore = E129,E226,E251 + +[flake8] +ignore = DW12,E226,E251,F401,F811 From 75518b08111d64104a3e24119a51d34415a4c017 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Thu, 24 Mar 2016 17:30:13 -0400 Subject: [PATCH 4/7] Remove the last _has_type_var(). --- src/typing.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/typing.py b/src/typing.py index 65c50287..bd3903af 100644 --- a/src/typing.py +++ b/src/typing.py @@ -113,9 +113,6 @@ def _eval_type(self, globalns, localns): """ return self - def _has_type_var(self): - return False - def _get_type_vars(self, tvars): pass From 373338dee3e049945080805d79578e17fb1cb40b Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Thu, 24 Mar 2016 17:31:05 -0400 Subject: [PATCH 5/7] Backport fixing the __module__ attribute of a NamedTuple. (Backport of 3819cbc1776b0553b1669f089ce9670546963126, CPython issue #25665.) --- python2/test_typing.py | 9 +++++++++ python2/typing.py | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/python2/test_typing.py b/python2/test_typing.py index 0ab679d1..73866f7a 100644 --- a/python2/test_typing.py +++ b/python2/test_typing.py @@ -1032,6 +1032,15 @@ def test_basics(self): assert Emp._fields == ('name', 'id') assert Emp._field_types == dict(name=str, id=int) + def test_pickle(self): + global Emp # pickle wants to reference the class by name + Emp = NamedTuple('Emp', [('name', str), ('id', int)]) + jane = Emp('jane', 37) + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + z = pickle.dumps(jane, proto) + jane2 = pickle.loads(z) + self.assertEqual(jane2, jane) + class IOTests(TestCase): diff --git a/python2/typing.py b/python2/typing.py index 3320b486..af5d0cc2 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -1531,6 +1531,11 @@ def NamedTuple(typename, fields): fields = [(n, t) for n, t in fields] cls = collections.namedtuple(typename, [n for n, t in fields]) cls._field_types = dict(fields) + # Set the module to the caller's module (otherwise it'd be 'typing'). + try: + cls.__module__ = sys._getframe(1).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): + pass return cls From 44a04885e9e202cf8fb09f7238e49ad3cad57943 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Thu, 24 Mar 2016 17:40:47 -0400 Subject: [PATCH 6/7] Backport test_nested(). --- python2/test_typing.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/python2/test_typing.py b/python2/test_typing.py index 73866f7a..3b36e7c7 100644 --- a/python2/test_typing.py +++ b/python2/test_typing.py @@ -684,6 +684,36 @@ class C(A[T, VT], Generic[VT, T, KT], B[KT, T]): assert C.__parameters__ == (VT, T, KT) + def test_nested(self): + + G = Generic + + class Visitor(G[T]): + + a = None + + def set(self, a): + self.a = a + + def get(self): + return self.a + + def visit(self): + return self.a + + V = Visitor[typing.List[int]] + + class IntListVisitor(V): + + def append(self, x): + self.a.append(x) + + a = IntListVisitor() + a.set([]) + a.append(1) + a.append(42) + assert a.get() == [1, 42] + def test_type_erasure(self): T = TypeVar('T') From e0d1c5dc659db9734c10a1b3c678df9df75fcda0 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Sat, 2 Apr 2016 16:45:04 -0500 Subject: [PATCH 7/7] Improve speed of Generic.__new__. Both PY2 and PY3. Fixes #196. --- python2/typing.py | 39 +++++++++++++++++++++++++++------------ src/typing.py | 36 +++++++++++++++++++++++++++--------- 2 files changed, 54 insertions(+), 21 deletions(-) diff --git a/python2/typing.py b/python2/typing.py index 055f6411..5e74d36b 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -891,6 +891,20 @@ def _geqv(a, b): return _gorg(a) is _gorg(b) +def _next_in_mro(cls): + """Helper for Generic.__new__. + + Returns the class after the last occurrence of Generic or + 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] + return next_in_mro + + class GenericMeta(TypingMeta, abc.ABCMeta): """Metaclass for generic types.""" @@ -919,9 +933,6 @@ def __new__(cls, name, bases, namespace, # and reject multiple Generic[...]. gvars = None for base in bases: - if base is object: - # Avoid checking for Generic in its own definition. - continue if base is Generic: raise TypeError("Cannot inherit from plain Generic") if (isinstance(base, GenericMeta) and @@ -950,6 +961,8 @@ def __new__(cls, name, bases, namespace, self.__extra__ = extra # Else __extra__ is inherited, eventually from the # (meta-)class default above. + # Speed hack (https://github.com/python/typing/issues/196). + self.__next_in_mro__ = _next_in_mro(self) return self def _get_type_vars(self, tvars): @@ -1077,6 +1090,10 @@ def __subclasscheck__(self, cls): return issubclass(cls, self.__extra__) +# Prevent checks for Generic to crash when defining Generic. +Generic = None + + class Generic(object): """Abstract base class for generic types. @@ -1102,16 +1119,13 @@ def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT: __slots__ = () def __new__(cls, *args, **kwds): - 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] - origin = _gorg(cls) - obj = next_in_mro.__new__(origin) - if origin is not cls: + if cls.__origin__ is None: + return cls.__next_in_mro__.__new__(cls) + else: + origin = _gorg(cls) + obj = cls.__next_in_mro__.__new__(origin) obj.__init__(*args, **kwds) - return obj + return obj def cast(typ, val): @@ -1305,6 +1319,7 @@ def _get_protocol_attrs(self): attr != '__args__' and attr != '__slots__' and attr != '_get_protocol_attrs' and + attr != '__next_in_mro__' and attr != '__parameters__' and attr != '__origin__' and attr != '__module__'): diff --git a/src/typing.py b/src/typing.py index bd2c7d60..d6f64bbc 100644 --- a/src/typing.py +++ b/src/typing.py @@ -873,6 +873,20 @@ def _geqv(a, b): return _gorg(a) is _gorg(b) +def _next_in_mro(cls): + """Helper for Generic.__new__. + + Returns the class after the last occurrence of Generic or + 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] + return next_in_mro + + class GenericMeta(TypingMeta, abc.ABCMeta): """Metaclass for generic types.""" @@ -929,6 +943,8 @@ def __new__(cls, name, bases, namespace, self.__extra__ = extra # Else __extra__ is inherited, eventually from the # (meta-)class default above. + # Speed hack (https://github.com/python/typing/issues/196). + self.__next_in_mro__ = _next_in_mro(self) return self def _get_type_vars(self, tvars): @@ -1056,6 +1072,10 @@ def __subclasscheck__(self, cls): return issubclass(cls, self.__extra__) +# Prevent checks for Generic to crash when defining Generic. +Generic = None + + class Generic(metaclass=GenericMeta): """Abstract base class for generic types. @@ -1080,16 +1100,13 @@ def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT: __slots__ = () def __new__(cls, *args, **kwds): - 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] - origin = _gorg(cls) - obj = next_in_mro.__new__(origin) - if origin is not cls: + if cls.__origin__ is None: + return cls.__next_in_mro__.__new__(cls) + else: + origin = _gorg(cls) + obj = cls.__next_in_mro__.__new__(origin) obj.__init__(*args, **kwds) - return obj + return obj def cast(typ, val): @@ -1283,6 +1300,7 @@ def _get_protocol_attrs(self): attr != '__args__' and attr != '__slots__' and attr != '_get_protocol_attrs' and + attr != '__next_in_mro__' and attr != '__parameters__' and attr != '__origin__' and attr != '__module__'):