diff options
Diffstat (limited to 'Lib/typing.py')
-rw-r--r-- | Lib/typing.py | 641 |
1 files changed, 302 insertions, 339 deletions
diff --git a/Lib/typing.py b/Lib/typing.py index 4676d28..925d9e4 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -89,6 +89,13 @@ def _qualname(x): return x.__name__ +def _trim_name(nm): + if nm.startswith('_') and nm not in ('_TypeAlias', + '_ForwardRef', '_TypingBase', '_FinalTypingBase'): + nm = nm[1:] + return nm + + class TypingMeta(type): """Metaclass for every type defined below. @@ -127,22 +134,69 @@ class TypingMeta(type): pass def __repr__(self): - return '%s.%s' % (self.__module__, _qualname(self)) + qname = _trim_name(_qualname(self)) + return '%s.%s' % (self.__module__, qname) + + +class _TypingBase(metaclass=TypingMeta, _root=True): + """Indicator of special typing constructs.""" + + __slots__ = () + + + def __init__(self, *args, **kwds): + pass + + def __new__(cls, *args, **kwds): + """Constructor. + + This only exists to give a better error message in case + someone tries to subclass a special typing object (not a good idea). + """ + if (len(args) == 3 and + isinstance(args[0], str) and + isinstance(args[1], tuple)): + # Close enough. + raise TypeError("Cannot subclass %r" % cls) + return object.__new__(cls) + + # Things that are not classes also need these. + def _eval_type(self, globalns, localns): + return self + + def _get_type_vars(self, tvars): + pass + + def __repr__(self): + cls = type(self) + qname = _trim_name(_qualname(cls)) + return '%s.%s' % (cls.__module__, qname) + def __call__(self, *args, **kwds): + raise TypeError("Cannot instantiate %r" % type(self)) -class Final: + +class _FinalTypingBase(_TypingBase, _root=True): """Mix-in class to prevent instantiation.""" __slots__ = () - def __new__(self, *args, **kwds): - raise TypeError("Cannot instantiate %r" % self.__class__) + def __new__(cls, *args, _root=False, **kwds): + self = super().__new__(cls, *args, **kwds) + if _root is True: + return self + raise TypeError("Cannot instantiate %r" % cls) -class _ForwardRef(TypingMeta): +class _ForwardRef(_TypingBase, _root=True): """Wrapper to hold a forward reference.""" - def __new__(cls, arg): + __slots__ = ('__forward_arg__', '__forward_code__', + '__forward_evaluated__', '__forward_value__', + '__forward_frame__') + + def __init__(self, arg): + super().__init__(arg) if not isinstance(arg, str): raise TypeError('ForwardRef must be a string -- got %r' % (arg,)) try: @@ -150,7 +204,6 @@ class _ForwardRef(TypingMeta): except SyntaxError: raise SyntaxError('ForwardRef must be an expression -- got %r' % (arg,)) - self = super().__new__(cls, arg, (), {}, _root=True) self.__forward_arg__ = arg self.__forward_code__ = code self.__forward_evaluated__ = False @@ -161,7 +214,6 @@ class _ForwardRef(TypingMeta): frame = frame.f_back assert frame is not None self.__forward_frame__ = frame - return self def _eval_type(self, globalns, localns): if not self.__forward_evaluated__: @@ -177,49 +229,36 @@ class _ForwardRef(TypingMeta): self.__forward_evaluated__ = True return self.__forward_value__ + def __eq__(self, other): + if not isinstance(other, _ForwardRef): + return NotImplemented + return (self.__forward_arg__ == other.__forward_arg__ and + self.__forward_frame__ == other.__forward_frame__) + + def __hash__(self): + return hash((self.__forward_arg__, self.__forward_frame__)) + def __instancecheck__(self, obj): raise TypeError("Forward references cannot be used with isinstance().") def __subclasscheck__(self, cls): - if not self.__forward_evaluated__: - globalns = self.__forward_frame__.f_globals - localns = self.__forward_frame__.f_locals - try: - self._eval_type(globalns, localns) - except NameError: - return False # Too early. - return issubclass(cls, self.__forward_value__) + raise TypeError("Forward references cannot be used with issubclass().") def __repr__(self): return '_ForwardRef(%r)' % (self.__forward_arg__,) -class _TypeAlias: +class _TypeAlias(_TypingBase, _root=True): """Internal helper class for defining generic variants of concrete types. - Note that this is not a type; let's call it a pseudo-type. It can - be used in instance and subclass checks, e.g. isinstance(m, Match) - or issubclass(type(m), Match). However, it cannot be itself the - target of an issubclass() call; e.g. issubclass(Match, C) (for - some arbitrary class C) raises TypeError rather than returning - False. + Note that this is not a type; let's call it a pseudo-type. It cannot + be used in instance and subclass checks in parameterized form, i.e. + ``isinstance(42, Match[str])`` raises ``TypeError`` instead of returning + ``False``. """ __slots__ = ('name', 'type_var', 'impl_type', 'type_checker') - def __new__(cls, *args, **kwds): - """Constructor. - - This only exists to give a better error message in case - 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)): - # Close enough. - raise TypeError("A type alias cannot be subclassed") - return object.__new__(cls) - def __init__(self, name, type_var, impl_type, type_checker): """Initializer. @@ -232,9 +271,9 @@ class _TypeAlias: and returns a value that should be a type_var instance. """ assert isinstance(name, str), repr(name) - assert isinstance(type_var, type), repr(type_var) assert isinstance(impl_type, type), repr(impl_type) assert not isinstance(impl_type, TypingMeta), repr(impl_type) + assert isinstance(type_var, (type, _TypingBase)) self.name = name self.type_var = type_var self.impl_type = impl_type @@ -244,36 +283,41 @@ class _TypeAlias: return "%s[%s]" % (self.name, _type_repr(self.type_var)) def __getitem__(self, parameter): - assert isinstance(parameter, type), repr(parameter) if not isinstance(self.type_var, TypeVar): raise TypeError("%s cannot be further parameterized." % self) - if self.type_var.__constraints__: - if not issubclass(parameter, Union[self.type_var.__constraints__]): + if self.type_var.__constraints__ and isinstance(parameter, type): + if not issubclass(parameter, self.type_var.__constraints__): raise TypeError("%s is not a valid substitution for %s." % (parameter, self.type_var)) + if isinstance(parameter, TypeVar): + raise TypeError("%s cannot be re-parameterized." % self.type_var) return self.__class__(self.name, parameter, self.impl_type, self.type_checker) + def __eq__(self, other): + if not isinstance(other, _TypeAlias): + return NotImplemented + return self.name == other.name and self.type_var == other.type_var + + def __hash__(self): + return hash((self.name, self.type_var)) + def __instancecheck__(self, obj): - raise TypeError("Type aliases cannot be used with isinstance().") + if not isinstance(self.type_var, TypeVar): + raise TypeError("Parameterized type aliases cannot be used " + "with isinstance().") + return isinstance(obj, self.impl_type) def __subclasscheck__(self, cls): - if cls is Any: - return True - if isinstance(cls, _TypeAlias): - # Covariance. For now, we compare by name. - return (cls.name == self.name and - issubclass(cls.type_var, self.type_var)) - else: - # Note that this is too lenient, because the - # implementation type doesn't carry information about - # whether it is about bytes or str (for example). - return issubclass(cls, self.impl_type) + if not isinstance(self.type_var, TypeVar): + raise TypeError("Parameterized type aliases cannot be used " + "with issubclass().") + return issubclass(cls, self.impl_type) def _get_type_vars(types, tvars): for t in types: - if isinstance(t, TypingMeta) or isinstance(t, _ClassVar): + if isinstance(t, TypingMeta) or isinstance(t, _TypingBase): t._get_type_vars(tvars) @@ -284,7 +328,7 @@ def _type_vars(types): def _eval_type(t, globalns, localns): - if isinstance(t, TypingMeta) or isinstance(t, _ClassVar): + if isinstance(t, TypingMeta) or isinstance(t, _TypingBase): return t._eval_type(globalns, localns) else: return t @@ -306,7 +350,7 @@ def _type_check(arg, msg): return type(None) if isinstance(arg, str): arg = _ForwardRef(arg) - if not isinstance(arg, (type, _TypeAlias)) and not callable(arg): + if not isinstance(arg, (type, _TypingBase)) and not callable(arg): raise TypeError(msg + " Got %.100r." % (arg,)) return arg @@ -328,23 +372,7 @@ def _type_repr(obj): return repr(obj) -class AnyMeta(TypingMeta): - """Metaclass for Any.""" - - def __new__(cls, name, bases, namespace, _root=False): - self = super().__new__(cls, name, bases, namespace, _root=_root) - return self - - def __instancecheck__(self, obj): - raise TypeError("Any cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - if not isinstance(cls, type): - return super().__subclasscheck__(cls) # To TypeError. - return True - - -class Any(Final, metaclass=AnyMeta, _root=True): +class _Any(_FinalTypingBase, _root=True): """Special type indicating an unconstrained type. - Any object is an instance of Any. @@ -354,8 +382,17 @@ class Any(Final, metaclass=AnyMeta, _root=True): __slots__ = () + def __instancecheck__(self, obj): + raise TypeError("Any cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError("Any cannot be used with issubclass().") + + +Any = _Any(_root=True) -class TypeVar(TypingMeta, metaclass=TypingMeta, _root=True): + +class TypeVar(_TypingBase, _root=True): """Type variable. Usage:: @@ -400,9 +437,14 @@ class TypeVar(TypingMeta, metaclass=TypingMeta, _root=True): A.__constraints__ == (str, bytes) """ - def __new__(cls, name, *constraints, bound=None, + __slots__ = ('__name__', '__bound__', '__constraints__', + '__covariant__', '__contravariant__') + + def __init__(self, name, *constraints, bound=None, covariant=False, contravariant=False): - self = super().__new__(cls, name, (Final,), {}, _root=True) + super().__init__(name, *constraints, bound=bound, + covariant=covariant, contravariant=contravariant) + self.__name__ = name if covariant and contravariant: raise ValueError("Bivariant types are not supported.") self.__covariant__ = bool(covariant) @@ -417,7 +459,6 @@ class TypeVar(TypingMeta, metaclass=TypingMeta, _root=True): self.__bound__ = _type_check(bound, "Bound must be a type.") else: self.__bound__ = None - return self def _get_type_vars(self, tvars): if self not in tvars: @@ -436,16 +477,7 @@ class TypeVar(TypingMeta, metaclass=TypingMeta, _root=True): raise TypeError("Type variables cannot be used with isinstance().") def __subclasscheck__(self, cls): - # TODO: Make this raise TypeError too? - if cls is self: - return True - if cls is Any: - return True - if self.__bound__ is not None: - return issubclass(cls, self.__bound__) - if self.__constraints__: - return any(issubclass(cls, c) for c in self.__constraints__) - return True + raise TypeError("Type variables cannot be used with issubclass().") # Some unconstrained type variables. These are used by the container types. @@ -463,19 +495,85 @@ T_contra = TypeVar('T_contra', contravariant=True) # Ditto contravariant. AnyStr = TypeVar('AnyStr', bytes, str) -class UnionMeta(TypingMeta): - """Metaclass for Union.""" +def _tp_cache(func): + cached = functools.lru_cache()(func) + @functools.wraps(func) + def inner(*args, **kwds): + try: + return cached(*args, **kwds) + except TypeError: + pass # Do not duplicate real errors. + return func(*args, **kwds) + return inner + + +class _Union(_FinalTypingBase, _root=True): + """Union type; Union[X, Y] means either X or Y. + + To define a union, use e.g. Union[int, str]. Details: + + - The arguments must be types and there must be at least one. + + - None as an argument is a special case and is replaced by + type(None). + + - Unions of unions are flattened, e.g.:: + + Union[Union[int, str], float] == Union[int, str, float] + + - Unions of a single argument vanish, e.g.:: + + Union[int] == int # The constructor actually returns int + + - Redundant arguments are skipped, e.g.:: + + Union[int, str, int] == Union[int, str] + + - When comparing unions, the argument order is ignored, e.g.:: - def __new__(cls, name, bases, namespace, parameters=None, _root=False): + Union[int, str] == Union[str, int] + + - When two arguments have a subclass relationship, the least + derived argument is kept, e.g.:: + + class Employee: pass + class Manager(Employee): pass + Union[int, Employee, Manager] == Union[int, Employee] + Union[Manager, int, Employee] == Union[int, Employee] + Union[Employee, Manager] == Employee + + - Corollary: if Any is present it is the sole survivor, e.g.:: + + Union[int, Any] == Any + + - Similar for object:: + + Union[int, object] == object + + - To cut a tie: Union[object, Any] == Union[Any, object] == Any. + + - 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]. + """ + + __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: - return super().__new__(cls, name, bases, namespace, _root=_root) + self.__union_params__ = None + self.__union_set_params__ = None + return self if not isinstance(parameters, tuple): raise TypeError("Expected parameters=<tuple>") # 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, UnionMeta): + if isinstance(p, _Union): params.extend(p.__union_params__) else: params.append(_type_check(p, msg)) @@ -499,22 +597,16 @@ class UnionMeta(TypingMeta): for t1 in params: if t1 is Any: return Any - if isinstance(t1, TypeVar): - continue - if isinstance(t1, _TypeAlias): - # _TypeAlias is not a real class. - continue if not isinstance(t1, type): - assert callable(t1) # A callable might sneak through. continue if any(isinstance(t2, type) and issubclass(t1, t2) - for t2 in all_params - {t1} if not isinstance(t2, TypeVar)): + 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() - # Create a new class with these params. - self = super().__new__(cls, name, bases, {}, _root=True) self.__union_params__ = tuple(t for t in params if t in all_params) self.__union_set_params__ = frozenset(self.__union_params__) return self @@ -525,8 +617,7 @@ class UnionMeta(TypingMeta): if p == self.__union_params__: return self else: - return self.__class__(self.__name__, self.__bases__, {}, - p, _root=True) + return self.__class__(p, _root=True) def _get_type_vars(self, tvars): if self.__union_params__: @@ -539,6 +630,7 @@ class UnionMeta(TypingMeta): for t in self.__union_params__)) return r + @_tp_cache def __getitem__(self, parameters): if self.__union_params__ is not None: raise TypeError( @@ -547,11 +639,10 @@ class UnionMeta(TypingMeta): raise TypeError("Cannot take a Union of no types.") if not isinstance(parameters, tuple): parameters = (parameters,) - return self.__class__(self.__name__, self.__bases__, - dict(self.__dict__), parameters, _root=True) + return self.__class__(parameters, _root=True) def __eq__(self, other): - if not isinstance(other, UnionMeta): + if not isinstance(other, _Union): return NotImplemented return self.__union_set_params__ == other.__union_set_params__ @@ -562,110 +653,45 @@ class UnionMeta(TypingMeta): raise TypeError("Unions cannot be used with isinstance().") def __subclasscheck__(self, cls): - if cls is Any: - return True - if self.__union_params__ is None: - return isinstance(cls, UnionMeta) - elif isinstance(cls, UnionMeta): - if cls.__union_params__ is None: - return False - return all(issubclass(c, self) for c in (cls.__union_params__)) - elif isinstance(cls, TypeVar): - if cls in self.__union_params__: - return True - if cls.__constraints__: - return issubclass(Union[cls.__constraints__], self) - return False - else: - return any(issubclass(cls, t) for t in self.__union_params__) + raise TypeError("Unions cannot be used with issubclass().") -class Union(Final, metaclass=UnionMeta, _root=True): - """Union type; Union[X, Y] means either X or Y. - - To define a union, use e.g. Union[int, str]. Details: - - - The arguments must be types and there must be at least one. - - - None as an argument is a special case and is replaced by - type(None). - - - Unions of unions are flattened, e.g.:: - - Union[Union[int, str], float] == Union[int, str, float] - - - Unions of a single argument vanish, e.g.:: - - Union[int] == int # The constructor actually returns int - - - Redundant arguments are skipped, e.g.:: - - Union[int, str, int] == Union[int, str] - - - When comparing unions, the argument order is ignored, e.g.:: - - Union[int, str] == Union[str, int] - - - When two arguments have a subclass relationship, the least - derived argument is kept, e.g.:: - - class Employee: pass - class Manager(Employee): pass - Union[int, Employee, Manager] == Union[int, Employee] - Union[Manager, int, Employee] == Union[int, Employee] - Union[Employee, Manager] == Employee - - - Corollary: if Any is present it is the sole survivor, e.g.:: - - Union[int, Any] == Any +Union = _Union(_root=True) - - Similar for object:: - Union[int, object] == object - - - To cut a tie: Union[object, Any] == Union[Any, object] == Any. - - - You cannot subclass or instantiate a union. - - - You cannot write Union[X][Y] (what would it mean?). +class _Optional(_FinalTypingBase, _root=True): + """Optional type. - - You can use Optional[X] as a shorthand for Union[X, None]. + Optional[X] is equivalent to Union[X, type(None)]. """ - # Unsubscripted Union type has params set to None. - __union_params__ = None - __union_set_params__ = None - - -class OptionalMeta(TypingMeta): - """Metaclass for Optional.""" - - def __new__(cls, name, bases, namespace, _root=False): - return super().__new__(cls, name, bases, namespace, _root=_root) + __slots__ = () + @_tp_cache def __getitem__(self, arg): arg = _type_check(arg, "Optional[t] requires a single type.") return Union[arg, type(None)] -class Optional(Final, metaclass=OptionalMeta, _root=True): - """Optional type. +Optional = _Optional(_root=True) - Optional[X] is equivalent to Union[X, type(None)]. - """ - __slots__ = () +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, ...]. + """ -class TupleMeta(TypingMeta): - """Metaclass for Tuple.""" + __slots__ = ('__tuple_params__', '__tuple_use_ellipsis__') - def __new__(cls, name, bases, namespace, parameters=None, + def __init__(self, parameters=None, use_ellipsis=False, _root=False): - self = super().__new__(cls, name, bases, namespace, _root=_root) self.__tuple_params__ = parameters self.__tuple_use_ellipsis__ = use_ellipsis - return self def _get_type_vars(self, tvars): if self.__tuple_params__: @@ -679,8 +705,7 @@ class TupleMeta(TypingMeta): if p == self.__tuple_params__: return self else: - return self.__class__(self.__name__, self.__bases__, {}, - p, _root=True) + return self.__class__(p, _root=True) def __repr__(self): r = super().__repr__() @@ -694,6 +719,7 @@ class TupleMeta(TypingMeta): ', '.join(params)) return r + @_tp_cache def __getitem__(self, parameters): if self.__tuple_params__ is not None: raise TypeError("Cannot re-parameterize %r" % (self,)) @@ -707,64 +733,50 @@ class TupleMeta(TypingMeta): 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__(self.__name__, self.__bases__, - dict(self.__dict__), parameters, + return self.__class__(parameters, use_ellipsis=use_ellipsis, _root=True) def __eq__(self, other): - if not isinstance(other, TupleMeta): + 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__) + return hash((self.__tuple_params__, self.__tuple_use_ellipsis__)) def __instancecheck__(self, obj): - raise TypeError("Tuples cannot be used with isinstance().") + if self.__tuple_params__ == None: + return isinstance(obj, tuple) + raise TypeError("Parameterized Tuple cannot be used " + "with isinstance().") def __subclasscheck__(self, cls): - if cls is Any: - return True - if not isinstance(cls, type): - return super().__subclasscheck__(cls) # To TypeError. - if issubclass(cls, tuple): - return True # Special case. - if not isinstance(cls, TupleMeta): - return super().__subclasscheck__(cls) # False. - if self.__tuple_params__ is None: - return True - if cls.__tuple_params__ is None: - return False # ??? - if cls.__tuple_use_ellipsis__ != self.__tuple_use_ellipsis__: - return False - # Covariance. - return (len(self.__tuple_params__) == len(cls.__tuple_params__) and - all(issubclass(x, p) - for x, p in zip(cls.__tuple_params__, - self.__tuple_params__))) - - -class Tuple(Final, metaclass=TupleMeta, _root=True): - """Tuple type; Tuple[X, Y] is the cross-product type of X and Y. + if self.__tuple_params__ == None: + return issubclass(cls, tuple) + raise TypeError("Parameterized Tuple cannot be used " + "with issubclass().") - 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 Sequence[T]. - """ +Tuple = _Tuple(_root=True) - __slots__ = () +class _Callable(_FinalTypingBase, _root=True): + """Callable type; Callable[[int], str] is a function of (int) -> str. -class CallableMeta(TypingMeta): - """Metaclass for Callable.""" + 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. - def __new__(cls, name, bases, namespace, _root=False, - args=None, result=None): + 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 # Must be 'class Callable'. + pass else: if args is not Ellipsis: if not isinstance(args, list): @@ -775,10 +787,8 @@ class CallableMeta(TypingMeta): 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 = super().__new__(cls, name, bases, namespace, _root=_root) self.__args__ = args self.__result__ = result - return self def _get_type_vars(self, tvars): if self.__args__ and self.__args__ is not Ellipsis: @@ -795,8 +805,7 @@ class CallableMeta(TypingMeta): if args == self.__args__ and result == self.__result__: return self else: - return self.__class__(self.__name__, self.__bases__, {}, - args=args, result=result, _root=True) + return self.__class__(args, result, _root=True) def __repr__(self): r = super().__repr__() @@ -816,12 +825,10 @@ class CallableMeta(TypingMeta): raise TypeError( "Callable must be used as Callable[[arg, ...], result].") args, result = parameters - return self.__class__(self.__name__, self.__bases__, - dict(self.__dict__), _root=True, - args=args, result=result) + return self.__class__(args, result, _root=True) def __eq__(self, other): - if not isinstance(other, CallableMeta): + if not isinstance(other, _Callable): return NotImplemented return (self.__args__ == other.__args__ and self.__result__ == other.__result__) @@ -836,31 +843,18 @@ class CallableMeta(TypingMeta): if self.__args__ is None and self.__result__ is None: return isinstance(obj, collections_abc.Callable) else: - raise TypeError("Callable[] cannot be used with isinstance().") + raise TypeError("Parameterized Callable cannot be used " + "with isinstance().") def __subclasscheck__(self, cls): - if cls is Any: - return True - if not isinstance(cls, CallableMeta): - return super().__subclasscheck__(cls) if self.__args__ is None and self.__result__ is None: - return True - # We're not doing covariance or contravariance -- this is *invariance*. - return self == cls - - -class Callable(Final, metaclass=CallableMeta, _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. + return issubclass(cls, collections_abc.Callable) + else: + raise TypeError("Parameterized Callable cannot be used " + "with issubclass().") - There is no syntax to indicate optional or keyword arguments, - such function types are rarely used as callback types. - """ - __slots__ = () +Callable = _Callable(_root=True) def _gorg(a): @@ -985,6 +979,7 @@ class GenericMeta(TypingMeta, abc.ABCMeta): def __hash__(self): return hash((self.__name__, self.__parameters__)) + @_tp_cache def __getitem__(self, params): if not isinstance(params, tuple): params = (params,) @@ -1040,44 +1035,18 @@ class GenericMeta(TypingMeta, abc.ABCMeta): return self.__subclasscheck__(instance.__class__) def __subclasscheck__(self, cls): - if cls is Any: - return True - if isinstance(cls, GenericMeta): - # For a covariant class C(Generic[T]), - # 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.__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__: - # Covariant -- p_cls must be a subclass of p_self. - if not issubclass(p_cls, p_self): - break - elif p_origin.__contravariant__: - # Contravariant. I think it's the opposite. :-) - if not issubclass(p_self, p_cls): - break - else: - # Invariant -- p_cls and p_self must equal. - if p_self != p_cls: - break - else: - # If the origin's parameter is not a typevar, - # insist on invariance. - if p_self != p_cls: - break - else: - return True - # If we break out of the loop, the superclass gets a chance. + if self is Generic: + raise TypeError("Class %r cannot be used with class " + "or instance checks" % self) + if (self.__origin__ is not None and + sys._getframe(1).f_globals['__name__'] != 'abc'): + raise TypeError("Parameterized generics cannot be used with class " + "or instance checks") if super().__subclasscheck__(cls): return True - if self.__extra__ is None or isinstance(cls, GenericMeta): - return False - return issubclass(cls, self.__extra__) + if self.__extra__ is not None: + return issubclass(cls, self.__extra__) + return False # Prevent checks for Generic to crash when defining Generic. @@ -1117,7 +1086,7 @@ class Generic(metaclass=GenericMeta): return obj -class _ClassVar(metaclass=TypingMeta, _root=True): +class _ClassVar(_FinalTypingBase, _root=True): """Special type construct to mark class variables. An annotation wrapped in ClassVar indicates that a given @@ -1134,36 +1103,35 @@ class _ClassVar(metaclass=TypingMeta, _root=True): be used with isinstance() or issubclass(). """ - def __init__(self, tp=None, _root=False): - cls = type(self) - if _root: - self.__type__ = tp - else: - raise TypeError('Cannot initialize {}'.format(cls.__name__[1:])) + __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 types.'.format(cls.__name__[1:])), + '{} 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): - return type(self)(_eval_type(self.__type__, globalns, localns), - _root=True) + 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): - cls = type(self) - if not self.__type__: - return '{}.{}'.format(cls.__module__, cls.__name__[1:]) - return '{}.{}[{}]'.format(cls.__module__, cls.__name__[1:], - _type_repr(self.__type__)) + 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__)) @@ -1614,52 +1582,52 @@ if hasattr(collections_abc, 'Collection'): if hasattr(collections_abc, 'Collection'): class AbstractSet(Collection[T_co], extra=collections_abc.Set): - pass + __slots__ = () else: class AbstractSet(Sized, Iterable[T_co], Container[T_co], extra=collections_abc.Set): - pass + __slots__ = () class MutableSet(AbstractSet[T], extra=collections_abc.MutableSet): - pass + __slots__ = () # NOTE: It is only covariant in the value type. if hasattr(collections_abc, 'Collection'): class Mapping(Collection[KT], Generic[KT, VT_co], extra=collections_abc.Mapping): - pass + __slots__ = () else: class Mapping(Sized, Iterable[KT], Container[KT], Generic[KT, VT_co], extra=collections_abc.Mapping): - pass + __slots__ = () class MutableMapping(Mapping[KT, VT], extra=collections_abc.MutableMapping): - pass + __slots__ = () if hasattr(collections_abc, 'Reversible'): if hasattr(collections_abc, 'Collection'): class Sequence(Reversible[T_co], Collection[T_co], extra=collections_abc.Sequence): - pass + __slots__ = () else: class Sequence(Sized, Reversible[T_co], Container[T_co], extra=collections_abc.Sequence): - pass + __slots__ = () else: class Sequence(Sized, Iterable[T_co], Container[T_co], extra=collections_abc.Sequence): - pass + __slots__ = () class MutableSequence(Sequence[T], extra=collections_abc.MutableSequence): - pass + __slots__ = () class ByteString(Sequence[int], extra=collections_abc.ByteString): - pass + __slots__ = () ByteString.register(type(memoryview(b''))) @@ -1667,6 +1635,8 @@ ByteString.register(type(memoryview(b''))) class List(list, MutableSequence[T], extra=list): + __slots__ = () + def __new__(cls, *args, **kwds): if _geqv(cls, List): raise TypeError("Type List cannot be instantiated; " @@ -1676,6 +1646,8 @@ class List(list, MutableSequence[T], extra=list): class Set(set, MutableSet[T], extra=set): + __slots__ = () + def __new__(cls, *args, **kwds): if _geqv(cls, Set): raise TypeError("Type Set cannot be instantiated; " @@ -1683,22 +1655,7 @@ class Set(set, MutableSet[T], extra=set): return set.__new__(cls, *args, **kwds) -class _FrozenSetMeta(GenericMeta): - """This metaclass ensures set is not a subclass of FrozenSet. - - Without this metaclass, set would be considered a subclass of - FrozenSet, because FrozenSet.__extra__ is collections.abc.Set, and - set is a subclass of that. - """ - - def __subclasscheck__(self, cls): - if issubclass(cls, Set): - return False - return super().__subclasscheck__(cls) - - -class FrozenSet(frozenset, AbstractSet[T_co], metaclass=_FrozenSetMeta, - extra=frozenset): +class FrozenSet(frozenset, AbstractSet[T_co], extra=frozenset): __slots__ = () def __new__(cls, *args, **kwds): @@ -1709,23 +1666,23 @@ class FrozenSet(frozenset, AbstractSet[T_co], metaclass=_FrozenSetMeta, class MappingView(Sized, Iterable[T_co], extra=collections_abc.MappingView): - pass + __slots__ = () class KeysView(MappingView[KT], AbstractSet[KT], extra=collections_abc.KeysView): - pass + __slots__ = () class ItemsView(MappingView[Tuple[KT, VT_co]], AbstractSet[Tuple[KT, VT_co]], Generic[KT, VT_co], extra=collections_abc.ItemsView): - pass + __slots__ = () class ValuesView(MappingView[VT_co], extra=collections_abc.ValuesView): - pass + __slots__ = () if hasattr(contextlib, 'AbstractContextManager'): @@ -1736,6 +1693,8 @@ if hasattr(contextlib, 'AbstractContextManager'): class Dict(dict, MutableMapping[KT, VT], extra=dict): + __slots__ = () + def __new__(cls, *args, **kwds): if _geqv(cls, Dict): raise TypeError("Type Dict cannot be instantiated; " @@ -1745,6 +1704,8 @@ class Dict(dict, MutableMapping[KT, VT], extra=dict): class DefaultDict(collections.defaultdict, MutableMapping[KT, VT], extra=collections.defaultdict): + __slots__ = () + def __new__(cls, *args, **kwds): if _geqv(cls, DefaultDict): raise TypeError("Type DefaultDict cannot be instantiated; " @@ -1800,6 +1761,8 @@ class Type(Generic[CT_co], extra=type): At this point the type checker knows that joe has type BasicUser. """ + __slots__ = () + def _make_nmtuple(name, types): nm_tpl = collections.namedtuple(name, [n for n, t in types]) |