mirror of
https://github.com/python/cpython.git
synced 2025-09-26 02:10:18 +00:00
Issue #28556: More typing.py updates from upstream. (3.5->3.6)
This commit is contained in:
commit
b75f48580f
2 changed files with 123 additions and 154 deletions
|
@ -378,6 +378,16 @@ class CallableTests(BaseTestCase):
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
type(c)()
|
type(c)()
|
||||||
|
|
||||||
|
def test_callable_wrong_forms(self):
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
Callable[[...], int]
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
Callable[(), int]
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
Callable[[()], int]
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
Callable[[int, 1], 2]
|
||||||
|
|
||||||
def test_callable_instance_works(self):
|
def test_callable_instance_works(self):
|
||||||
def f():
|
def f():
|
||||||
pass
|
pass
|
||||||
|
@ -1296,9 +1306,10 @@ PY36 = sys.version_info[:2] >= (3, 6)
|
||||||
|
|
||||||
PY36_TESTS = """
|
PY36_TESTS = """
|
||||||
from test import ann_module, ann_module2, ann_module3
|
from test import ann_module, ann_module2, ann_module3
|
||||||
from collections import ChainMap
|
|
||||||
|
|
||||||
class B:
|
class A:
|
||||||
|
y: float
|
||||||
|
class B(A):
|
||||||
x: ClassVar[Optional['B']] = None
|
x: ClassVar[Optional['B']] = None
|
||||||
y: int
|
y: int
|
||||||
class CSub(B):
|
class CSub(B):
|
||||||
|
@ -1317,6 +1328,15 @@ if PY36:
|
||||||
gth = get_type_hints
|
gth = get_type_hints
|
||||||
|
|
||||||
class GetTypeHintTests(BaseTestCase):
|
class GetTypeHintTests(BaseTestCase):
|
||||||
|
def test_get_type_hints_from_various_objects(self):
|
||||||
|
# For invalid objects should fail with TypeError (not AttributeError etc).
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
gth(123)
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
gth('abc')
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
gth(None)
|
||||||
|
|
||||||
@skipUnless(PY36, 'Python 3.6 required')
|
@skipUnless(PY36, 'Python 3.6 required')
|
||||||
def test_get_type_hints_modules(self):
|
def test_get_type_hints_modules(self):
|
||||||
self.assertEqual(gth(ann_module), {1: 2, 'f': Tuple[int, int], 'x': int, 'y': str})
|
self.assertEqual(gth(ann_module), {1: 2, 'f': Tuple[int, int], 'x': int, 'y': str})
|
||||||
|
@ -1326,18 +1346,15 @@ class GetTypeHintTests(BaseTestCase):
|
||||||
@skipUnless(PY36, 'Python 3.6 required')
|
@skipUnless(PY36, 'Python 3.6 required')
|
||||||
def test_get_type_hints_classes(self):
|
def test_get_type_hints_classes(self):
|
||||||
self.assertEqual(gth(ann_module.C, ann_module.__dict__),
|
self.assertEqual(gth(ann_module.C, ann_module.__dict__),
|
||||||
ChainMap({'y': Optional[ann_module.C]}, {}))
|
{'y': Optional[ann_module.C]})
|
||||||
self.assertEqual(repr(gth(ann_module.j_class)), 'ChainMap({}, {})')
|
self.assertIsInstance(gth(ann_module.j_class), dict)
|
||||||
self.assertEqual(gth(ann_module.M), ChainMap({'123': 123, 'o': type},
|
self.assertEqual(gth(ann_module.M), {'123': 123, 'o': type})
|
||||||
{}, {}))
|
|
||||||
self.assertEqual(gth(ann_module.D),
|
self.assertEqual(gth(ann_module.D),
|
||||||
ChainMap({'j': str, 'k': str,
|
{'j': str, 'k': str, 'y': Optional[ann_module.C]})
|
||||||
'y': Optional[ann_module.C]}, {}))
|
self.assertEqual(gth(ann_module.Y), {'z': int})
|
||||||
self.assertEqual(gth(ann_module.Y), ChainMap({'z': int}, {}))
|
|
||||||
self.assertEqual(gth(ann_module.h_class),
|
self.assertEqual(gth(ann_module.h_class),
|
||||||
ChainMap({}, {'y': Optional[ann_module.C]}, {}))
|
{'y': Optional[ann_module.C]})
|
||||||
self.assertEqual(gth(ann_module.S), ChainMap({'x': str, 'y': str},
|
self.assertEqual(gth(ann_module.S), {'x': str, 'y': str})
|
||||||
{}))
|
|
||||||
self.assertEqual(gth(ann_module.foo), {'x': int})
|
self.assertEqual(gth(ann_module.foo), {'x': int})
|
||||||
|
|
||||||
@skipUnless(PY36, 'Python 3.6 required')
|
@skipUnless(PY36, 'Python 3.6 required')
|
||||||
|
@ -1355,20 +1372,34 @@ class GetTypeHintTests(BaseTestCase):
|
||||||
class Der(ABase): ...
|
class Der(ABase): ...
|
||||||
self.assertEqual(gth(ABase.meth), {'x': int})
|
self.assertEqual(gth(ABase.meth), {'x': int})
|
||||||
|
|
||||||
|
def test_get_type_hints_for_builins(self):
|
||||||
|
# Should not fail for built-in classes and functions.
|
||||||
|
self.assertEqual(gth(int), {})
|
||||||
|
self.assertEqual(gth(type), {})
|
||||||
|
self.assertEqual(gth(dir), {})
|
||||||
|
self.assertEqual(gth(len), {})
|
||||||
|
|
||||||
def test_previous_behavior(self):
|
def test_previous_behavior(self):
|
||||||
def testf(x, y): ...
|
def testf(x, y): ...
|
||||||
testf.__annotations__['x'] = 'int'
|
testf.__annotations__['x'] = 'int'
|
||||||
self.assertEqual(gth(testf), {'x': int})
|
self.assertEqual(gth(testf), {'x': int})
|
||||||
|
|
||||||
|
def test_get_type_hints_for_object_with_annotations(self):
|
||||||
|
class A: ...
|
||||||
|
class B: ...
|
||||||
|
b = B()
|
||||||
|
b.__annotations__ = {'x': 'A'}
|
||||||
|
self.assertEqual(gth(b, locals()), {'x': A})
|
||||||
|
|
||||||
@skipUnless(PY36, 'Python 3.6 required')
|
@skipUnless(PY36, 'Python 3.6 required')
|
||||||
def test_get_type_hints_ClassVar(self):
|
def test_get_type_hints_ClassVar(self):
|
||||||
|
self.assertEqual(gth(ann_module2.CV, ann_module2.__dict__),
|
||||||
|
{'var': typing.ClassVar[ann_module2.CV]})
|
||||||
self.assertEqual(gth(B, globals()),
|
self.assertEqual(gth(B, globals()),
|
||||||
ChainMap({'y': int, 'x': ClassVar[Optional[B]]}, {}))
|
{'y': int, 'x': ClassVar[Optional[B]]})
|
||||||
self.assertEqual(gth(CSub, globals()),
|
self.assertEqual(gth(CSub, globals()),
|
||||||
ChainMap({'z': ClassVar[CSub]},
|
{'z': ClassVar[CSub], 'y': int, 'x': ClassVar[Optional[B]]})
|
||||||
{'y': int, 'x': ClassVar[Optional[B]]}, {}))
|
self.assertEqual(gth(G), {'lst': ClassVar[List[T]]})
|
||||||
self.assertEqual(gth(G), ChainMap({'lst': ClassVar[List[T]]},{},{}))
|
|
||||||
|
|
||||||
|
|
||||||
class CollectionsAbcTests(BaseTestCase):
|
class CollectionsAbcTests(BaseTestCase):
|
||||||
|
|
214
Lib/typing.py
214
Lib/typing.py
|
@ -10,8 +10,6 @@ try:
|
||||||
import collections.abc as collections_abc
|
import collections.abc as collections_abc
|
||||||
except ImportError:
|
except ImportError:
|
||||||
import collections as collections_abc # Fallback for PY3.2.
|
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.
|
# Please keep __all__ alphabetized within each category.
|
||||||
|
@ -1194,14 +1192,12 @@ class CallableMeta(GenericMeta):
|
||||||
# super()._tree_repr() for nice formatting.
|
# super()._tree_repr() for nice formatting.
|
||||||
arg_list = []
|
arg_list = []
|
||||||
for arg in tree[1:]:
|
for arg in tree[1:]:
|
||||||
if arg == ():
|
if not isinstance(arg, tuple):
|
||||||
arg_list.append('[]')
|
|
||||||
elif not isinstance(arg, tuple):
|
|
||||||
arg_list.append(_type_repr(arg))
|
arg_list.append(_type_repr(arg))
|
||||||
else:
|
else:
|
||||||
arg_list.append(arg[0]._tree_repr(arg))
|
arg_list.append(arg[0]._tree_repr(arg))
|
||||||
if len(arg_list) == 2:
|
if arg_list[0] == '...':
|
||||||
return repr(tree[0]) + '[%s]' % ', '.join(arg_list)
|
return repr(tree[0]) + '[..., %s]' % arg_list[1]
|
||||||
return (repr(tree[0]) +
|
return (repr(tree[0]) +
|
||||||
'[[%s], %s]' % (', '.join(arg_list[:-1]), arg_list[-1]))
|
'[[%s], %s]' % (', '.join(arg_list[:-1]), arg_list[-1]))
|
||||||
|
|
||||||
|
@ -1216,26 +1212,22 @@ class CallableMeta(GenericMeta):
|
||||||
raise TypeError("Callable must be used as "
|
raise TypeError("Callable must be used as "
|
||||||
"Callable[[arg, ...], result].")
|
"Callable[[arg, ...], result].")
|
||||||
args, result = parameters
|
args, result = parameters
|
||||||
if args is ...:
|
if args is Ellipsis:
|
||||||
parameters = (..., result)
|
parameters = (Ellipsis, result)
|
||||||
elif args == []:
|
|
||||||
parameters = ((), result)
|
|
||||||
else:
|
else:
|
||||||
if not isinstance(args, list):
|
if not isinstance(args, list):
|
||||||
raise TypeError("Callable[args, result]: args must be a list."
|
raise TypeError("Callable[args, result]: args must be a list."
|
||||||
" Got %.100r." % (args,))
|
" Got %.100r." % (args,))
|
||||||
parameters = tuple(args) + (result,)
|
parameters = (tuple(args), result)
|
||||||
return self.__getitem_inner__(parameters)
|
return self.__getitem_inner__(parameters)
|
||||||
|
|
||||||
@_tp_cache
|
@_tp_cache
|
||||||
def __getitem_inner__(self, parameters):
|
def __getitem_inner__(self, parameters):
|
||||||
*args, result = parameters
|
args, result = parameters
|
||||||
msg = "Callable[args, result]: result must be a type."
|
msg = "Callable[args, result]: result must be a type."
|
||||||
result = _type_check(result, msg)
|
result = _type_check(result, msg)
|
||||||
if args == [...,]:
|
if args is Ellipsis:
|
||||||
return super().__getitem__((_TypingEllipsis, result))
|
return super().__getitem__((_TypingEllipsis, result))
|
||||||
if args == [(),]:
|
|
||||||
return super().__getitem__((_TypingEmpty, result))
|
|
||||||
msg = "Callable[[arg, ...], result]: each arg must be a type."
|
msg = "Callable[[arg, ...], result]: each arg must be a type."
|
||||||
args = tuple(_type_check(arg, msg) for arg in args)
|
args = tuple(_type_check(arg, msg) for arg in args)
|
||||||
parameters = args + (result,)
|
parameters = args + (result,)
|
||||||
|
@ -1332,7 +1324,11 @@ def cast(typ, val):
|
||||||
|
|
||||||
def _get_defaults(func):
|
def _get_defaults(func):
|
||||||
"""Internal helper to extract the default arguments, by name."""
|
"""Internal helper to extract the default arguments, by name."""
|
||||||
code = func.__code__
|
try:
|
||||||
|
code = func.__code__
|
||||||
|
except AttributeError:
|
||||||
|
# Some built-in functions don't have __code__, __defaults__, etc.
|
||||||
|
return {}
|
||||||
pos_count = code.co_argcount
|
pos_count = code.co_argcount
|
||||||
arg_names = code.co_varnames
|
arg_names = code.co_varnames
|
||||||
arg_names = arg_names[:pos_count]
|
arg_names = arg_names[:pos_count]
|
||||||
|
@ -1346,138 +1342,80 @@ def _get_defaults(func):
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
if sys.version_info[:2] >= (3, 3):
|
def get_type_hints(obj, globalns=None, localns=None):
|
||||||
def get_type_hints(obj, globalns=None, localns=None):
|
"""Return type hints for an object.
|
||||||
"""Return type hints for an object.
|
|
||||||
|
|
||||||
This is often the same as obj.__annotations__, but it handles
|
This is often the same as obj.__annotations__, but it handles
|
||||||
forward references encoded as string literals, and if necessary
|
forward references encoded as string literals, and if necessary
|
||||||
adds Optional[t] if a default value equal to None is set.
|
adds Optional[t] if a default value equal to None is set.
|
||||||
|
|
||||||
The argument may be a module, class, method, or function. The annotations
|
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
|
are returned as a dictionary. For classes, annotations include also
|
||||||
dictionaries.
|
inherited members.
|
||||||
|
|
||||||
TypeError is raised if the argument is not of a type that can contain
|
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
|
annotations, and an empty dictionary is returned if no annotations are
|
||||||
present.
|
present.
|
||||||
|
|
||||||
BEWARE -- the behavior of globalns and localns is counterintuitive
|
BEWARE -- the behavior of globalns and localns is counterintuitive
|
||||||
(unless you are familiar with how eval() and exec() work). The
|
(unless you are familiar with how eval() and exec() work). The
|
||||||
search order is locals first, then globals.
|
search order is locals first, then globals.
|
||||||
|
|
||||||
- If no dict arguments are passed, an attempt is made to use the
|
- 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
|
globals from obj, and these are also used as the locals. If the
|
||||||
object does not appear to have globals, an exception is raised.
|
object does not appear to have globals, an exception is raised.
|
||||||
|
|
||||||
- If one dict argument is passed, it is used for both globals and
|
- If one dict argument is passed, it is used for both globals and
|
||||||
locals.
|
locals.
|
||||||
|
|
||||||
- If two dict arguments are passed, they specify globals and
|
- If two dict arguments are passed, they specify globals and
|
||||||
locals, respectively.
|
locals, respectively.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if getattr(obj, '__no_type_check__', None):
|
if getattr(obj, '__no_type_check__', None):
|
||||||
return {}
|
return {}
|
||||||
if globalns is None:
|
if globalns is None:
|
||||||
globalns = getattr(obj, '__globals__', {})
|
globalns = getattr(obj, '__globals__', {})
|
||||||
if localns is None:
|
if localns is None:
|
||||||
localns = globalns
|
|
||||||
elif localns is None:
|
|
||||||
localns = globalns
|
localns = globalns
|
||||||
|
elif localns is None:
|
||||||
|
localns = globalns
|
||||||
|
# Classes require a special treatment.
|
||||||
|
if isinstance(obj, type):
|
||||||
|
hints = {}
|
||||||
|
for base in reversed(obj.__mro__):
|
||||||
|
ann = base.__dict__.get('__annotations__', {})
|
||||||
|
for name, value in ann.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
|
||||||
|
hints = getattr(obj, '__annotations__', None)
|
||||||
|
if hints is None:
|
||||||
|
# Return empty annotations for something that _could_ have them.
|
||||||
if (isinstance(obj, types.FunctionType) or
|
if (isinstance(obj, types.FunctionType) or
|
||||||
isinstance(obj, types.BuiltinFunctionType) or
|
isinstance(obj, types.BuiltinFunctionType) or
|
||||||
isinstance(obj, types.MethodType)):
|
isinstance(obj, types.MethodType) or
|
||||||
defaults = _get_defaults(obj)
|
isinstance(obj, types.ModuleType)):
|
||||||
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 {}
|
|
||||||
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 {}
|
return {}
|
||||||
if globalns is None:
|
else:
|
||||||
globalns = getattr(obj, '__globals__', {})
|
raise TypeError('{!r} is not a module, class, method, '
|
||||||
if localns is None:
|
'or function.'.format(obj))
|
||||||
localns = globalns
|
defaults = _get_defaults(obj)
|
||||||
elif localns is None:
|
hints = dict(hints)
|
||||||
localns = globalns
|
for name, value in hints.items():
|
||||||
defaults = _get_defaults(obj)
|
if value is None:
|
||||||
hints = dict(obj.__annotations__)
|
value = type(None)
|
||||||
for name, value in hints.items():
|
if isinstance(value, str):
|
||||||
if isinstance(value, str):
|
value = _ForwardRef(value)
|
||||||
value = _ForwardRef(value)
|
value = _eval_type(value, globalns, localns)
|
||||||
value = _eval_type(value, globalns, localns)
|
if name in defaults and defaults[name] is None:
|
||||||
if name in defaults and defaults[name] is None:
|
value = Optional[value]
|
||||||
value = Optional[value]
|
hints[name] = value
|
||||||
hints[name] = value
|
return hints
|
||||||
return hints
|
|
||||||
|
|
||||||
|
|
||||||
def no_type_check(arg):
|
def no_type_check(arg):
|
||||||
|
@ -2160,7 +2098,7 @@ class TextIO(IO[str]):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractproperty
|
@abstractproperty
|
||||||
def errors(self) -> str:
|
def errors(self) -> Optional[str]:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractproperty
|
@abstractproperty
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue