summaryrefslogtreecommitdiff
path: root/Lib/typing.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/typing.py')
-rw-r--r--Lib/typing.py146
1 files changed, 133 insertions, 13 deletions
diff --git a/Lib/typing.py b/Lib/typing.py
index 5573a1fbf9..6ce74fc9f1 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -6,6 +6,7 @@ import functools
import re as stdlib_re # Avoid confusion with the re we export.
import sys
import types
+
try:
import collections.abc as collections_abc
except ImportError:
@@ -17,6 +18,7 @@ __all__ = [
# Super-special typing primitives.
'Any',
'Callable',
+ 'ClassVar',
'Generic',
'Optional',
'Tuple',
@@ -270,7 +272,7 @@ class _TypeAlias:
def _get_type_vars(types, tvars):
for t in types:
- if isinstance(t, TypingMeta):
+ if isinstance(t, TypingMeta) or isinstance(t, _ClassVar):
t._get_type_vars(tvars)
@@ -281,7 +283,7 @@ def _type_vars(types):
def _eval_type(t, globalns, localns):
- if isinstance(t, TypingMeta):
+ if isinstance(t, TypingMeta) or isinstance(t, _ClassVar):
return t._eval_type(globalns, localns)
else:
return t
@@ -1114,6 +1116,67 @@ class Generic(metaclass=GenericMeta):
return obj
+class _ClassVar(metaclass=TypingMeta, _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().
+ """
+
+ def __init__(self, tp=None, _root=False):
+ cls = type(self)
+ if _root:
+ self.__type__ = tp
+ else:
+ raise TypeError('Cannot initialize {}'.format(cls.__name__[1:]))
+
+ def __getitem__(self, item):
+ cls = type(self)
+ if self.__type__ is None:
+ return cls(_type_check(item,
+ '{} accepts only types.'.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)
+
+ 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__))
+
+ 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.
@@ -1142,12 +1205,20 @@ def _get_defaults(func):
def get_type_hints(obj, globalns=None, localns=None):
- """Return type hints for a function or method object.
+ """Return type hints for an object.
This is often the same as obj.__annotations__, but it handles
forward references encoded as string literals, and if necessary
adds Optional[t] if a default value equal to None is set.
+ The argument may be a module, class, method, or function. The annotations
+ are returned as a dictionary, or in the case of a class, a ChainMap of
+ dictionaries.
+
+ TypeError is raised if the argument is not of a type that can contain
+ annotations, and an empty dictionary is returned if no annotations are
+ present.
+
BEWARE -- the behavior of globalns and localns is counterintuitive
(unless you are familiar with how eval() and exec() work). The
search order is locals first, then globals.
@@ -1162,6 +1233,7 @@ def get_type_hints(obj, globalns=None, localns=None):
- If two dict arguments are passed, they specify globals and
locals, respectively.
"""
+
if getattr(obj, '__no_type_check__', None):
return {}
if globalns is None:
@@ -1170,16 +1242,62 @@ def get_type_hints(obj, globalns=None, localns=None):
localns = globalns
elif localns is None:
localns = globalns
- defaults = _get_defaults(obj)
- hints = dict(obj.__annotations__)
- for name, value in hints.items():
- if isinstance(value, str):
- value = _ForwardRef(value)
- value = _eval_type(value, globalns, localns)
- if name in defaults and defaults[name] is None:
- value = Optional[value]
- hints[name] = value
- return hints
+
+ if (isinstance(obj, types.FunctionType) or
+ isinstance(obj, types.BuiltinFunctionType) or
+ isinstance(obj, types.MethodType)):
+ defaults = _get_defaults(obj)
+ hints = obj.__annotations__
+ for name, value in hints.items():
+ if value is None:
+ value = type(None)
+ if isinstance(value, str):
+ value = _ForwardRef(value)
+ value = _eval_type(value, globalns, localns)
+ if name in defaults and defaults[name] is None:
+ value = Optional[value]
+ hints[name] = value
+ return hints
+
+ if isinstance(obj, types.ModuleType):
+ try:
+ hints = obj.__annotations__
+ except AttributeError:
+ return {}
+ # we keep only those annotations that can be accessed on module
+ members = obj.__dict__
+ hints = {name: value for name, value in hints.items()
+ if name in members}
+ for name, value in hints.items():
+ if value is None:
+ value = type(None)
+ if isinstance(value, str):
+ value = _ForwardRef(value)
+ value = _eval_type(value, globalns, localns)
+ hints[name] = value
+ return hints
+
+ if isinstance(object, type):
+ cmap = None
+ for base in reversed(obj.__mro__):
+ new_map = collections.ChainMap if cmap is None else cmap.new_child
+ try:
+ hints = base.__dict__['__annotations__']
+ except KeyError:
+ cmap = new_map()
+ else:
+ for name, value in hints.items():
+ if value is None:
+ value = type(None)
+ if isinstance(value, str):
+ value = _ForwardRef(value)
+ value = _eval_type(value, globalns, localns)
+ hints[name] = value
+ cmap = new_map(hints)
+ return cmap
+
+ raise TypeError('{!r} is not a module, class, method, '
+ 'or function.'.format(obj))
def no_type_check(arg):
@@ -1300,6 +1418,8 @@ class _ProtocolMeta(GenericMeta):
else:
if (not attr.startswith('_abc_') and
attr != '__abstractmethods__' and
+ attr != '__annotations__' and
+ attr != '__weakref__' and
attr != '_is_protocol' and
attr != '__dict__' and
attr != '__args__' and