Issue #28556: More typing.py updates from upstream.

This commit is contained in:
Guido van Rossum 2016-11-09 13:12:51 -08:00
parent a1a8b7d3d7
commit 991d14fee1
2 changed files with 123 additions and 154 deletions

View File

@ -378,6 +378,16 @@ class CallableTests(BaseTestCase):
with self.assertRaises(TypeError):
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 f():
pass
@ -1296,9 +1306,10 @@ PY36 = sys.version_info[:2] >= (3, 6)
PY36_TESTS = """
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
y: int
class CSub(B):
@ -1317,6 +1328,15 @@ if PY36:
gth = get_type_hints
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')
def test_get_type_hints_modules(self):
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')
def test_get_type_hints_classes(self):
self.assertEqual(gth(ann_module.C, ann_module.__dict__),
ChainMap({'y': Optional[ann_module.C]}, {}))
self.assertEqual(repr(gth(ann_module.j_class)), 'ChainMap({}, {})')
self.assertEqual(gth(ann_module.M), ChainMap({'123': 123, 'o': type},
{}, {}))
{'y': Optional[ann_module.C]})
self.assertIsInstance(gth(ann_module.j_class), dict)
self.assertEqual(gth(ann_module.M), {'123': 123, 'o': type})
self.assertEqual(gth(ann_module.D),
ChainMap({'j': str, 'k': str,
'y': Optional[ann_module.C]}, {}))
self.assertEqual(gth(ann_module.Y), ChainMap({'z': int}, {}))
{'j': str, 'k': str, 'y': Optional[ann_module.C]})
self.assertEqual(gth(ann_module.Y), {'z': int})
self.assertEqual(gth(ann_module.h_class),
ChainMap({}, {'y': Optional[ann_module.C]}, {}))
self.assertEqual(gth(ann_module.S), ChainMap({'x': str, 'y': str},
{}))
{'y': Optional[ann_module.C]})
self.assertEqual(gth(ann_module.S), {'x': str, 'y': str})
self.assertEqual(gth(ann_module.foo), {'x': int})
@skipUnless(PY36, 'Python 3.6 required')
@ -1355,20 +1372,34 @@ class GetTypeHintTests(BaseTestCase):
class Der(ABase): ...
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 testf(x, y): ...
testf.__annotations__['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')
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()),
ChainMap({'y': int, 'x': ClassVar[Optional[B]]}, {}))
{'y': int, 'x': ClassVar[Optional[B]]})
self.assertEqual(gth(CSub, globals()),
ChainMap({'z': ClassVar[CSub]},
{'y': int, 'x': ClassVar[Optional[B]]}, {}))
self.assertEqual(gth(G), ChainMap({'lst': ClassVar[List[T]]},{},{}))
{'z': ClassVar[CSub], 'y': int, 'x': ClassVar[Optional[B]]})
self.assertEqual(gth(G), {'lst': ClassVar[List[T]]})
class CollectionsAbcTests(BaseTestCase):

View File

@ -10,8 +10,6 @@ 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.
@ -1194,14 +1192,12 @@ class CallableMeta(GenericMeta):
# super()._tree_repr() for nice formatting.
arg_list = []
for arg in tree[1:]:
if arg == ():
arg_list.append('[]')
elif not isinstance(arg, tuple):
if not isinstance(arg, tuple):
arg_list.append(_type_repr(arg))
else:
arg_list.append(arg[0]._tree_repr(arg))
if len(arg_list) == 2:
return repr(tree[0]) + '[%s]' % ', '.join(arg_list)
if arg_list[0] == '...':
return repr(tree[0]) + '[..., %s]' % arg_list[1]
return (repr(tree[0]) +
'[[%s], %s]' % (', '.join(arg_list[:-1]), arg_list[-1]))
@ -1216,26 +1212,22 @@ class CallableMeta(GenericMeta):
raise TypeError("Callable must be used as "
"Callable[[arg, ...], result].")
args, result = parameters
if args is ...:
parameters = (..., result)
elif args == []:
parameters = ((), result)
if args is Ellipsis:
parameters = (Ellipsis, result)
else:
if not isinstance(args, list):
raise TypeError("Callable[args, result]: args must be a list."
" Got %.100r." % (args,))
parameters = tuple(args) + (result,)
parameters = (tuple(args), result)
return self.__getitem_inner__(parameters)
@_tp_cache
def __getitem_inner__(self, parameters):
*args, result = parameters
args, result = parameters
msg = "Callable[args, result]: result must be a type."
result = _type_check(result, msg)
if args == [...,]:
if args is Ellipsis:
return super().__getitem__((_TypingEllipsis, result))
if args == [(),]:
return super().__getitem__((_TypingEmpty, result))
msg = "Callable[[arg, ...], result]: each arg must be a type."
args = tuple(_type_check(arg, msg) for arg in args)
parameters = args + (result,)
@ -1332,7 +1324,11 @@ def cast(typ, val):
def _get_defaults(func):
"""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
arg_names = code.co_varnames
arg_names = arg_names[:pos_count]
@ -1346,138 +1342,80 @@ def _get_defaults(func):
return res
if sys.version_info[:2] >= (3, 3):
def get_type_hints(obj, globalns=None, localns=None):
"""Return type hints for an object.
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. For classes, annotations include also
inherited members.
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:
localns = globalns
elif 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
# 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
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 {}
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):
isinstance(obj, types.MethodType) or
isinstance(obj, types.ModuleType)):
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 = 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
else:
raise TypeError('{!r} is not a module, class, method, '
'or function.'.format(obj))
defaults = _get_defaults(obj)
hints = dict(hints)
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
def no_type_check(arg):
@ -2160,7 +2098,7 @@ class TextIO(IO[str]):
pass
@abstractproperty
def errors(self) -> str:
def errors(self) -> Optional[str]:
pass
@abstractproperty