Issue #28079: Update typing and test typing from python/typing repo.
Ivan Levkivskyi (3.6 version)
diff --git a/Lib/typing.py b/Lib/typing.py
index 6ce74fc..4676d28 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -6,11 +6,12 @@
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:
import collections as collections_abc # Fallback for PY3.2.
+if sys.version_info[:2] >= (3, 3):
+ from collections import ChainMap
# Please keep __all__ alphabetized within each category.
@@ -1204,53 +1205,135 @@
return res
-def get_type_hints(obj, globalns=None, localns=None):
- """Return type hints for an object.
+if sys.version_info[:2] >= (3, 3):
+ def get_type_hints(obj, globalns=None, localns=None):
+ """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.
+ 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.
+ 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.
+ 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.
+ 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.
- - If no dict arguments are passed, an attempt is made to use the
- globals from obj, and these are also used as the locals. If the
- object does not appear to have globals, an exception is raised.
+ - If no dict arguments are passed, an attempt is made to use the
+ globals from obj, and these are also used as the locals. If the
+ object does not appear to have globals, an exception is raised.
- - If one dict argument is passed, it is used for both globals and
- locals.
+ - If one dict argument is passed, it is used for both globals and
+ locals.
- - If two dict arguments are passed, they specify globals and
- locals, respectively.
- """
+ - If two dict arguments are passed, they specify globals and
+ locals, respectively.
+ """
- if getattr(obj, '__no_type_check__', None):
- return {}
- if globalns is None:
- globalns = getattr(obj, '__globals__', {})
- if localns is None:
+ if getattr(obj, '__no_type_check__', None):
+ return {}
+ if globalns is None:
+ globalns = getattr(obj, '__globals__', {})
+ if localns is None:
+ localns = globalns
+ elif localns is None:
localns = globalns
- elif localns is None:
- localns = globalns
- if (isinstance(obj, types.FunctionType) or
- isinstance(obj, types.BuiltinFunctionType) or
- isinstance(obj, types.MethodType)):
+ 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))
+
+else:
+ def get_type_hints(obj, globalns=None, localns=None):
+ """Return type hints for a function or method 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.
+
+ 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.
+
+ - If no dict arguments are passed, an attempt is made to use the
+ globals from obj, and these are also used as the locals. If the
+ object does not appear to have globals, an exception is raised.
+
+ - If one dict argument is passed, it is used for both globals and
+ locals.
+
+ - If two dict arguments are passed, they specify globals and
+ locals, respectively.
+ """
+ if getattr(obj, '__no_type_check__', None):
+ return {}
+ if globalns is None:
+ globalns = getattr(obj, '__globals__', {})
+ if localns is None:
+ localns = globalns
+ elif localns is None:
+ localns = globalns
defaults = _get_defaults(obj)
- hints = obj.__annotations__
+ hints = dict(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)
@@ -1259,62 +1342,30 @@
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):
"""Decorator to indicate that annotations are not type hints.
The argument must be a class or function; if it is a class, it
- applies recursively to all methods defined in that class (but not
- to methods defined in its superclasses or subclasses).
+ applies recursively to all methods and classes defined in that class
+ (but not to methods defined in its superclasses or subclasses).
- This mutates the function(s) in place.
+ This mutates the function(s) or class(es) in place.
"""
if isinstance(arg, type):
- for obj in arg.__dict__.values():
+ arg_attrs = arg.__dict__.copy()
+ for attr, val in arg.__dict__.items():
+ if val in arg.__bases__:
+ arg_attrs.pop(attr)
+ for obj in arg_attrs.values():
if isinstance(obj, types.FunctionType):
obj.__no_type_check__ = True
- else:
+ if isinstance(obj, type):
+ no_type_check(obj)
+ try:
arg.__no_type_check__ = True
+ except TypeError: # built-in classes
+ pass
return arg
@@ -1725,7 +1776,7 @@
# This is not a real generic class. Don't use outside annotations.
-class Type(type, Generic[CT_co], extra=type):
+class Type(Generic[CT_co], extra=type):
"""A special construct usable to annotate class objects.
For example, suppose we have the following classes::
@@ -1750,31 +1801,66 @@
"""
-def NamedTuple(typename, fields):
- """Typed version of namedtuple.
-
- Usage::
-
- Employee = typing.NamedTuple('Employee', [('name', str), 'id', int)])
-
- This is equivalent to::
-
- Employee = collections.namedtuple('Employee', ['name', 'id'])
-
- The resulting class has one extra attribute: _field_types,
- giving a dict mapping field names to types. (The field names
- are in the _fields attribute, which is part of the namedtuple
- API.)
- """
- 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').
+def _make_nmtuple(name, types):
+ nm_tpl = collections.namedtuple(name, [n for n, t in types])
+ nm_tpl._field_types = dict(types)
try:
- cls.__module__ = sys._getframe(1).f_globals.get('__name__', '__main__')
+ nm_tpl.__module__ = sys._getframe(2).f_globals.get('__name__', '__main__')
except (AttributeError, ValueError):
pass
- return cls
+ return nm_tpl
+
+
+if sys.version_info[:2] >= (3, 6):
+ class NamedTupleMeta(type):
+
+ def __new__(cls, typename, bases, ns, *, _root=False):
+ if _root:
+ return super().__new__(cls, typename, bases, ns)
+ types = ns.get('__annotations__', {})
+ return _make_nmtuple(typename, types.items())
+
+ class NamedTuple(metaclass=NamedTupleMeta, _root=True):
+ """Typed version of namedtuple.
+
+ Usage::
+
+ class Employee(NamedTuple):
+ name: str
+ id: int
+
+ This is equivalent to::
+
+ Employee = collections.namedtuple('Employee', ['name', 'id'])
+
+ The resulting class has one extra attribute: _field_types,
+ giving a dict mapping field names to types. (The field names
+ are in the _fields attribute, which is part of the namedtuple
+ API.) Backward-compatible usage::
+
+ Employee = NamedTuple('Employee', [('name', str), ('id', int)])
+ """
+
+ def __new__(self, typename, fields):
+ return _make_nmtuple(typename, fields)
+else:
+ def NamedTuple(typename, fields):
+ """Typed version of namedtuple.
+
+ Usage::
+
+ Employee = typing.NamedTuple('Employee', [('name', str), 'id', int)])
+
+ This is equivalent to::
+
+ Employee = collections.namedtuple('Employee', ['name', 'id'])
+
+ The resulting class has one extra attribute: _field_types,
+ giving a dict mapping field names to types. (The field names
+ are in the _fields attribute, which is part of the namedtuple
+ API.)
+ """
+ return _make_nmtuple(typename, fields)
def NewType(name, tp):