bpo-38605: Make 'from __future__ import annotations' the default (GH-20434)
The hard part was making all the tests pass; there are some subtle issues here, because apparently the future import wasn't tested very thoroughly in previous Python versions. For example, `inspect.signature()` returned type objects normally (except for forward references), but strings with the future import. We changed it to try and return type objects by calling `typing.get_type_hints()`, but fall back on returning strings if that function fails (which it may do if there are future references in the annotations that require passing in a specific namespace to resolve).
This commit is contained in:
parent
bef7d299eb
commit
044a1048ca
|
@ -610,13 +610,9 @@ following the parameter name. Any parameter may have an annotation, even those
|
|||
``*identifier`` or ``**identifier``. Functions may have "return" annotation of
|
||||
the form "``-> expression``" after the parameter list. These annotations can be
|
||||
any valid Python expression. The presence of annotations does not change the
|
||||
semantics of a function. The annotation values are available as values of
|
||||
a dictionary keyed by the parameters' names in the :attr:`__annotations__`
|
||||
attribute of the function object. If the ``annotations`` import from
|
||||
:mod:`__future__` is used, annotations are preserved as strings at runtime which
|
||||
enables postponed evaluation. Otherwise, they are evaluated when the function
|
||||
definition is executed. In this case annotations may be evaluated in
|
||||
a different order than they appear in the source code.
|
||||
semantics of a function. The annotation values are available as string values
|
||||
in a dictionary keyed by the parameters' names in the :attr:`__annotations__`
|
||||
attribute of the function object.
|
||||
|
||||
.. index:: pair: lambda; expression
|
||||
|
||||
|
|
|
@ -70,6 +70,23 @@ Summary -- Release highlights
|
|||
New Features
|
||||
============
|
||||
|
||||
.. _whatsnew310-pep563:
|
||||
|
||||
PEP 563: Postponed Evaluation of Annotations Becomes Default
|
||||
------------------------------------------------------------
|
||||
|
||||
In Python 3.7, postponed evaluation of annotations was added,
|
||||
to be enabled with a ``from __future__ import annotations``
|
||||
directive. In 3.10 this became the default behavior, even
|
||||
without that future directive. With this being default, all
|
||||
annotations stored in :attr:`__annotations__` will be strings.
|
||||
If needed, annotations can be resolved at runtime using
|
||||
:func:`typing.get_type_hints`. See :pep:`563` for a full
|
||||
description. Also, the :func:`inspect.signature` will try to
|
||||
resolve types from now on, and when it fails it will fall back to
|
||||
showing the string annotations. (Contributed by Batuhan Taskaya
|
||||
in :issue:`38605`.)
|
||||
|
||||
* The :class:`int` type has a new method :meth:`int.bit_count`, returning the
|
||||
number of ones in the binary expansion of a given integer, also known
|
||||
as the population count. (Contributed by Niklas Fiekas in :issue:`29882`.)
|
||||
|
|
|
@ -399,8 +399,10 @@ def _create_fn(name, args, body, *, globals=None, locals=None,
|
|||
|
||||
ns = {}
|
||||
exec(txt, globals, ns)
|
||||
return ns['__create_fn__'](**locals)
|
||||
|
||||
func = ns['__create_fn__'](**locals)
|
||||
for arg, annotation in func.__annotations__.copy().items():
|
||||
func.__annotations__[arg] = locals[annotation]
|
||||
return func
|
||||
|
||||
def _field_assign(frozen, name, value, self_name):
|
||||
# If we're a frozen class, then assign to our fields in __init__
|
||||
|
@ -651,6 +653,11 @@ def _is_type(annotation, cls, a_module, a_type, is_type_predicate):
|
|||
# a eval() penalty for every single field of every dataclass
|
||||
# that's defined. It was judged not worth it.
|
||||
|
||||
# Strip away the extra quotes as a result of double-stringifying when the
|
||||
# 'annotations' feature became default.
|
||||
if annotation.startswith(("'", '"')) and annotation.endswith(("'", '"')):
|
||||
annotation = annotation[1:-1]
|
||||
|
||||
match = _MODULE_IDENTIFIER_RE.match(annotation)
|
||||
if match:
|
||||
ns = None
|
||||
|
@ -991,7 +998,7 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen):
|
|||
if not getattr(cls, '__doc__'):
|
||||
# Create a class doc-string.
|
||||
cls.__doc__ = (cls.__name__ +
|
||||
str(inspect.signature(cls)).replace(' -> None', ''))
|
||||
str(inspect.signature(cls)).replace(' -> NoneType', ''))
|
||||
|
||||
abc.update_abstractmethods(cls)
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@ import sys
|
|||
import tokenize
|
||||
import token
|
||||
import types
|
||||
import typing
|
||||
import warnings
|
||||
import functools
|
||||
import builtins
|
||||
|
@ -1877,7 +1878,10 @@ def _signature_is_functionlike(obj):
|
|||
code = getattr(obj, '__code__', None)
|
||||
defaults = getattr(obj, '__defaults__', _void) # Important to use _void ...
|
||||
kwdefaults = getattr(obj, '__kwdefaults__', _void) # ... and not None here
|
||||
annotations = getattr(obj, '__annotations__', None)
|
||||
try:
|
||||
annotations = _get_type_hints(obj)
|
||||
except AttributeError:
|
||||
annotations = None
|
||||
|
||||
return (isinstance(code, types.CodeType) and
|
||||
isinstance(name, str) and
|
||||
|
@ -2118,6 +2122,16 @@ def _signature_fromstr(cls, obj, s, skip_bound_arg=True):
|
|||
|
||||
return cls(parameters, return_annotation=cls.empty)
|
||||
|
||||
def _get_type_hints(func):
|
||||
try:
|
||||
return typing.get_type_hints(func)
|
||||
except Exception:
|
||||
# First, try to use the get_type_hints to resolve
|
||||
# annotations. But for keeping the behavior intact
|
||||
# if there was a problem with that (like the namespace
|
||||
# can't resolve some annotation) continue to use
|
||||
# string annotations
|
||||
return func.__annotations__
|
||||
|
||||
def _signature_from_builtin(cls, func, skip_bound_arg=True):
|
||||
"""Private helper function to get signature for
|
||||
|
@ -2161,7 +2175,8 @@ def _signature_from_function(cls, func, skip_bound_arg=True):
|
|||
positional = arg_names[:pos_count]
|
||||
keyword_only_count = func_code.co_kwonlyargcount
|
||||
keyword_only = arg_names[pos_count:pos_count + keyword_only_count]
|
||||
annotations = func.__annotations__
|
||||
annotations = _get_type_hints(func)
|
||||
|
||||
defaults = func.__defaults__
|
||||
kwdefaults = func.__kwdefaults__
|
||||
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
#from __future__ import annotations
|
||||
USING_STRINGS = False
|
||||
|
||||
# dataclass_module_1.py and dataclass_module_1_str.py are identical
|
||||
# except only the latter uses string annotations.
|
||||
|
||||
import dataclasses
|
||||
import typing
|
||||
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
from __future__ import annotations
|
||||
USING_STRINGS = True
|
||||
|
||||
# dataclass_module_1.py and dataclass_module_1_str.py are identical
|
||||
# except only the latter uses string annotations.
|
||||
|
||||
import dataclasses
|
||||
import typing
|
||||
|
||||
T_CV2 = typing.ClassVar[int]
|
||||
T_CV3 = typing.ClassVar
|
||||
|
||||
T_IV2 = dataclasses.InitVar[int]
|
||||
T_IV3 = dataclasses.InitVar
|
||||
|
||||
@dataclasses.dataclass
|
||||
class CV:
|
||||
T_CV4 = typing.ClassVar
|
||||
cv0: typing.ClassVar[int] = 20
|
||||
cv1: typing.ClassVar = 30
|
||||
cv2: T_CV2
|
||||
cv3: T_CV3
|
||||
not_cv4: T_CV4 # When using string annotations, this field is not recognized as a ClassVar.
|
||||
|
||||
@dataclasses.dataclass
|
||||
class IV:
|
||||
T_IV4 = dataclasses.InitVar
|
||||
iv0: dataclasses.InitVar[int]
|
||||
iv1: dataclasses.InitVar
|
||||
iv2: T_IV2
|
||||
iv3: T_IV3
|
||||
not_iv4: T_IV4 # When using string annotations, this field is not recognized as an InitVar.
|
|
@ -1,9 +1,3 @@
|
|||
#from __future__ import annotations
|
||||
USING_STRINGS = False
|
||||
|
||||
# dataclass_module_2.py and dataclass_module_2_str.py are identical
|
||||
# except only the latter uses string annotations.
|
||||
|
||||
from dataclasses import dataclass, InitVar
|
||||
from typing import ClassVar
|
||||
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
from __future__ import annotations
|
||||
USING_STRINGS = True
|
||||
|
||||
# dataclass_module_2.py and dataclass_module_2_str.py are identical
|
||||
# except only the latter uses string annotations.
|
||||
|
||||
from dataclasses import dataclass, InitVar
|
||||
from typing import ClassVar
|
||||
|
||||
T_CV2 = ClassVar[int]
|
||||
T_CV3 = ClassVar
|
||||
|
||||
T_IV2 = InitVar[int]
|
||||
T_IV3 = InitVar
|
||||
|
||||
@dataclass
|
||||
class CV:
|
||||
T_CV4 = ClassVar
|
||||
cv0: ClassVar[int] = 20
|
||||
cv1: ClassVar = 30
|
||||
cv2: T_CV2
|
||||
cv3: T_CV3
|
||||
not_cv4: T_CV4 # When using string annotations, this field is not recognized as a ClassVar.
|
||||
|
||||
@dataclass
|
||||
class IV:
|
||||
T_IV4 = InitVar
|
||||
iv0: InitVar[int]
|
||||
iv1: InitVar
|
||||
iv2: T_IV2
|
||||
iv3: T_IV3
|
||||
not_iv4: T_IV4 # When using string annotations, this field is not recognized as an InitVar.
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,228 @@
|
|||
import unittest
|
||||
import sys
|
||||
from textwrap import dedent
|
||||
|
||||
class PostponedAnnotationsTestCase(unittest.TestCase):
|
||||
template = dedent(
|
||||
"""
|
||||
def f() -> {ann}:
|
||||
...
|
||||
def g(arg: {ann}) -> None:
|
||||
...
|
||||
async def f2() -> {ann}:
|
||||
...
|
||||
async def g2(arg: {ann}) -> None:
|
||||
...
|
||||
var: {ann}
|
||||
var2: {ann} = None
|
||||
"""
|
||||
)
|
||||
|
||||
def getActual(self, annotation):
|
||||
scope = {}
|
||||
exec(self.template.format(ann=annotation), {}, scope)
|
||||
func_ret_ann = scope['f'].__annotations__['return']
|
||||
func_arg_ann = scope['g'].__annotations__['arg']
|
||||
async_func_ret_ann = scope['f2'].__annotations__['return']
|
||||
async_func_arg_ann = scope['g2'].__annotations__['arg']
|
||||
var_ann1 = scope['__annotations__']['var']
|
||||
var_ann2 = scope['__annotations__']['var2']
|
||||
self.assertEqual(func_ret_ann, func_arg_ann)
|
||||
self.assertEqual(func_ret_ann, async_func_ret_ann)
|
||||
self.assertEqual(func_ret_ann, async_func_arg_ann)
|
||||
self.assertEqual(func_ret_ann, var_ann1)
|
||||
self.assertEqual(func_ret_ann, var_ann2)
|
||||
return func_ret_ann
|
||||
|
||||
def assertAnnotationEqual(
|
||||
self, annotation, expected=None, drop_parens=False, is_tuple=False,
|
||||
):
|
||||
actual = self.getActual(annotation)
|
||||
if expected is None:
|
||||
expected = annotation if not is_tuple else annotation[1:-1]
|
||||
if drop_parens:
|
||||
self.assertNotEqual(actual, expected)
|
||||
actual = actual.replace("(", "").replace(")", "")
|
||||
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_annotations(self):
|
||||
eq = self.assertAnnotationEqual
|
||||
eq('...')
|
||||
eq("'some_string'")
|
||||
eq("u'some_string'")
|
||||
eq("b'\\xa3'")
|
||||
eq('Name')
|
||||
eq('None')
|
||||
eq('True')
|
||||
eq('False')
|
||||
eq('1')
|
||||
eq('1.0')
|
||||
eq('1j')
|
||||
eq('True or False')
|
||||
eq('True or False or None')
|
||||
eq('True and False')
|
||||
eq('True and False and None')
|
||||
eq('Name1 and Name2 or Name3')
|
||||
eq('Name1 and (Name2 or Name3)')
|
||||
eq('Name1 or Name2 and Name3')
|
||||
eq('(Name1 or Name2) and Name3')
|
||||
eq('Name1 and Name2 or Name3 and Name4')
|
||||
eq('Name1 or Name2 and Name3 or Name4')
|
||||
eq('a + b + (c + d)')
|
||||
eq('a * b * (c * d)')
|
||||
eq('(a ** b) ** c ** d')
|
||||
eq('v1 << 2')
|
||||
eq('1 >> v2')
|
||||
eq('1 % finished')
|
||||
eq('1 + v2 - v3 * 4 ^ 5 ** v6 / 7 // 8')
|
||||
eq('not great')
|
||||
eq('not not great')
|
||||
eq('~great')
|
||||
eq('+value')
|
||||
eq('++value')
|
||||
eq('-1')
|
||||
eq('~int and not v1 ^ 123 + v2 | True')
|
||||
eq('a + (not b)')
|
||||
eq('lambda: None')
|
||||
eq('lambda arg: None')
|
||||
eq('lambda a=True: a')
|
||||
eq('lambda a, b, c=True: a')
|
||||
eq("lambda a, b, c=True, *, d=1 << v2, e='str': a")
|
||||
eq("lambda a, b, c=True, *vararg, d, e='str', **kwargs: a + b")
|
||||
eq("lambda a, /, b, c=True, *vararg, d, e='str', **kwargs: a + b")
|
||||
eq('lambda x, /: x')
|
||||
eq('lambda x=1, /: x')
|
||||
eq('lambda x, /, y: x + y')
|
||||
eq('lambda x=1, /, y=2: x + y')
|
||||
eq('lambda x, /, y=1: x + y')
|
||||
eq('lambda x, /, y=1, *, z=3: x + y + z')
|
||||
eq('lambda x=1, /, y=2, *, z=3: x + y + z')
|
||||
eq('lambda x=1, /, y=2, *, z: x + y + z')
|
||||
eq('lambda x=1, y=2, z=3, /, w=4, *, l, l2: x + y + z + w + l + l2')
|
||||
eq('lambda x=1, y=2, z=3, /, w=4, *, l, l2, **kwargs: x + y + z + w + l + l2')
|
||||
eq('lambda x, /, y=1, *, z: x + y + z')
|
||||
eq('lambda x: lambda y: x + y')
|
||||
eq('1 if True else 2')
|
||||
eq('str or None if int or True else str or bytes or None')
|
||||
eq('str or None if (1 if True else 2) else str or bytes or None')
|
||||
eq("0 if not x else 1 if x > 0 else -1")
|
||||
eq("(1 if x > 0 else -1) if x else 0")
|
||||
eq("{'2.7': dead, '3.7': long_live or die_hard}")
|
||||
eq("{'2.7': dead, '3.7': long_live or die_hard, **{'3.6': verygood}}")
|
||||
eq("{**a, **b, **c}")
|
||||
eq("{'2.7', '3.6', '3.7', '3.8', '3.9', '4.0' if gilectomy else '3.10'}")
|
||||
eq("{*a, *b, *c}")
|
||||
eq("({'a': 'b'}, True or False, +value, 'string', b'bytes') or None")
|
||||
eq("()")
|
||||
eq("(a,)")
|
||||
eq("(a, b)")
|
||||
eq("(a, b, c)")
|
||||
eq("(*a, *b, *c)")
|
||||
eq("[]")
|
||||
eq("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10 or A, 11 or B, 12 or C]")
|
||||
eq("[*a, *b, *c]")
|
||||
eq("{i for i in (1, 2, 3)}")
|
||||
eq("{i ** 2 for i in (1, 2, 3)}")
|
||||
eq("{i ** 2 for i, _ in ((1, 'a'), (2, 'b'), (3, 'c'))}")
|
||||
eq("{i ** 2 + j for i in (1, 2, 3) for j in (1, 2, 3)}")
|
||||
eq("[i for i in (1, 2, 3)]")
|
||||
eq("[i ** 2 for i in (1, 2, 3)]")
|
||||
eq("[i ** 2 for i, _ in ((1, 'a'), (2, 'b'), (3, 'c'))]")
|
||||
eq("[i ** 2 + j for i in (1, 2, 3) for j in (1, 2, 3)]")
|
||||
eq("(i for i in (1, 2, 3))")
|
||||
eq("(i ** 2 for i in (1, 2, 3))")
|
||||
eq("(i ** 2 for i, _ in ((1, 'a'), (2, 'b'), (3, 'c')))")
|
||||
eq("(i ** 2 + j for i in (1, 2, 3) for j in (1, 2, 3))")
|
||||
eq("{i: 0 for i in (1, 2, 3)}")
|
||||
eq("{i: j for i, j in ((1, 'a'), (2, 'b'), (3, 'c'))}")
|
||||
eq("[(x, y) for x, y in (a, b)]")
|
||||
eq("[(x,) for x, in (a,)]")
|
||||
eq("Python3 > Python2 > COBOL")
|
||||
eq("Life is Life")
|
||||
eq("call()")
|
||||
eq("call(arg)")
|
||||
eq("call(kwarg='hey')")
|
||||
eq("call(arg, kwarg='hey')")
|
||||
eq("call(arg, *args, another, kwarg='hey')")
|
||||
eq("call(arg, another, kwarg='hey', **kwargs, kwarg2='ho')")
|
||||
eq("lukasz.langa.pl")
|
||||
eq("call.me(maybe)")
|
||||
eq("1 .real")
|
||||
eq("1.0.real")
|
||||
eq("....__class__")
|
||||
eq("list[str]")
|
||||
eq("dict[str, int]")
|
||||
eq("set[str,]")
|
||||
eq("tuple[str, ...]")
|
||||
eq("tuple[(str, *types)]")
|
||||
eq("tuple[str, int, (str, int)]")
|
||||
eq("tuple[(*int, str, str, (str, int))]")
|
||||
eq("tuple[str, int, float, dict[str, int]]")
|
||||
eq("slice[0]")
|
||||
eq("slice[0:1]")
|
||||
eq("slice[0:1:2]")
|
||||
eq("slice[:]")
|
||||
eq("slice[:-1]")
|
||||
eq("slice[1:]")
|
||||
eq("slice[::-1]")
|
||||
eq("slice[:,]")
|
||||
eq("slice[1:2,]")
|
||||
eq("slice[1:2:3,]")
|
||||
eq("slice[1:2, 1]")
|
||||
eq("slice[1:2, 2, 3]")
|
||||
eq("slice[()]")
|
||||
eq("slice[a, b:c, d:e:f]")
|
||||
eq("slice[(x for x in a)]")
|
||||
eq('str or None if sys.version_info[0] > (3,) else str or bytes or None')
|
||||
eq("f'f-string without formatted values is just a string'")
|
||||
eq("f'{{NOT a formatted value}}'")
|
||||
eq("f'some f-string with {a} {few():.2f} {formatted.values!r}'")
|
||||
eq('''f"{f'{nested} inner'} outer"''')
|
||||
eq("f'space between opening braces: { {a for a in (1, 2, 3)}}'")
|
||||
eq("f'{(lambda x: x)}'")
|
||||
eq("f'{(None if a else lambda x: x)}'")
|
||||
eq("f'{x}'")
|
||||
eq("f'{x!r}'")
|
||||
eq("f'{x!a}'")
|
||||
eq('(yield from outside_of_generator)')
|
||||
eq('(yield)')
|
||||
eq('(yield a + b)')
|
||||
eq('await some.complicated[0].call(with_args=True or 1 is not 1)')
|
||||
eq('[x for x in (a if b else c)]')
|
||||
eq('[x for x in a if (b if c else d)]')
|
||||
eq('f(x for x in a)')
|
||||
eq('f(1, (x for x in a))')
|
||||
eq('f((x for x in a), 2)')
|
||||
eq('(((a)))', 'a')
|
||||
eq('(((a, b)))', '(a, b)')
|
||||
eq("(x := 10)")
|
||||
eq("f'{(x := 10):=10}'")
|
||||
eq("1 + 2")
|
||||
eq("1 + 2 + 3")
|
||||
|
||||
def test_fstring_debug_annotations(self):
|
||||
# f-strings with '=' don't round trip very well, so set the expected
|
||||
# result explicitely.
|
||||
self.assertAnnotationEqual("f'{x=!r}'", expected="f'x={x!r}'")
|
||||
self.assertAnnotationEqual("f'{x=:}'", expected="f'x={x:}'")
|
||||
self.assertAnnotationEqual("f'{x=:.2f}'", expected="f'x={x:.2f}'")
|
||||
self.assertAnnotationEqual("f'{x=!r}'", expected="f'x={x!r}'")
|
||||
self.assertAnnotationEqual("f'{x=!a}'", expected="f'x={x!a}'")
|
||||
self.assertAnnotationEqual("f'{x=!s:*^20}'", expected="f'x={x!s:*^20}'")
|
||||
|
||||
def test_infinity_numbers(self):
|
||||
inf = "1e" + repr(sys.float_info.max_10_exp + 1)
|
||||
infj = f"{inf}j"
|
||||
self.assertAnnotationEqual("1e1000", expected=inf)
|
||||
self.assertAnnotationEqual("1e1000j", expected=infj)
|
||||
self.assertAnnotationEqual("-1e1000", expected=f"-{inf}")
|
||||
self.assertAnnotationEqual("3+1e1000j", expected=f"3 + {infj}")
|
||||
self.assertAnnotationEqual("(1e1000, 1e1000j)", expected=f"({inf}, {infj})")
|
||||
self.assertAnnotationEqual("'inf'")
|
||||
self.assertAnnotationEqual("('inf', 1e1000, 'infxxx', 1e1000j)", expected=f"('inf', {inf}, 'infxxx', {infj})")
|
||||
self.assertAnnotationEqual("(1e1000, (1e1000j,))", expected=f"({inf}, ({infj},))")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
|
@ -91,10 +91,6 @@ class AsyncBadSyntaxTest(unittest.TestCase):
|
|||
pass
|
||||
""",
|
||||
|
||||
"""async def foo(a:await something()):
|
||||
pass
|
||||
""",
|
||||
|
||||
"""async def foo():
|
||||
def bar():
|
||||
[i async for i in els]
|
||||
|
@ -299,10 +295,6 @@ class AsyncBadSyntaxTest(unittest.TestCase):
|
|||
pass
|
||||
""",
|
||||
|
||||
"""async def foo(a:await b):
|
||||
pass
|
||||
""",
|
||||
|
||||
"""def baz():
|
||||
async def foo(a=await b):
|
||||
pass
|
||||
|
|
|
@ -9,6 +9,7 @@ import pickle
|
|||
import inspect
|
||||
import builtins
|
||||
import unittest
|
||||
from textwrap import dedent
|
||||
from unittest.mock import Mock
|
||||
from typing import ClassVar, Any, List, Union, Tuple, Dict, Generic, TypeVar, Optional
|
||||
from typing import get_type_hints
|
||||
|
@ -562,17 +563,17 @@ class TestCase(unittest.TestCase):
|
|||
self.assertEqual(len(the_fields), 3)
|
||||
|
||||
self.assertEqual(the_fields[0].name, 'x')
|
||||
self.assertEqual(the_fields[0].type, int)
|
||||
self.assertEqual(the_fields[0].type, 'int')
|
||||
self.assertFalse(hasattr(C, 'x'))
|
||||
self.assertTrue (the_fields[0].init)
|
||||
self.assertTrue (the_fields[0].repr)
|
||||
self.assertEqual(the_fields[1].name, 'y')
|
||||
self.assertEqual(the_fields[1].type, str)
|
||||
self.assertEqual(the_fields[1].type, 'str')
|
||||
self.assertIsNone(getattr(C, 'y'))
|
||||
self.assertFalse(the_fields[1].init)
|
||||
self.assertTrue (the_fields[1].repr)
|
||||
self.assertEqual(the_fields[2].name, 'z')
|
||||
self.assertEqual(the_fields[2].type, str)
|
||||
self.assertEqual(the_fields[2].type, 'str')
|
||||
self.assertFalse(hasattr(C, 'z'))
|
||||
self.assertTrue (the_fields[2].init)
|
||||
self.assertFalse(the_fields[2].repr)
|
||||
|
@ -758,11 +759,11 @@ class TestCase(unittest.TestCase):
|
|||
def validate_class(cls):
|
||||
# First, check __annotations__, even though they're not
|
||||
# function annotations.
|
||||
self.assertEqual(cls.__annotations__['i'], int)
|
||||
self.assertEqual(cls.__annotations__['j'], str)
|
||||
self.assertEqual(cls.__annotations__['k'], F)
|
||||
self.assertEqual(cls.__annotations__['l'], float)
|
||||
self.assertEqual(cls.__annotations__['z'], complex)
|
||||
self.assertEqual(cls.__annotations__['i'], 'int')
|
||||
self.assertEqual(cls.__annotations__['j'], 'str')
|
||||
self.assertEqual(cls.__annotations__['k'], 'F')
|
||||
self.assertEqual(cls.__annotations__['l'], 'float')
|
||||
self.assertEqual(cls.__annotations__['z'], 'complex')
|
||||
|
||||
# Verify __init__.
|
||||
|
||||
|
@ -777,22 +778,22 @@ class TestCase(unittest.TestCase):
|
|||
self.assertEqual(param.name, 'self')
|
||||
param = next(params)
|
||||
self.assertEqual(param.name, 'i')
|
||||
self.assertIs (param.annotation, int)
|
||||
self.assertIs (param.annotation, 'int')
|
||||
self.assertEqual(param.default, inspect.Parameter.empty)
|
||||
self.assertEqual(param.kind, inspect.Parameter.POSITIONAL_OR_KEYWORD)
|
||||
param = next(params)
|
||||
self.assertEqual(param.name, 'j')
|
||||
self.assertIs (param.annotation, str)
|
||||
self.assertIs (param.annotation, 'str')
|
||||
self.assertEqual(param.default, inspect.Parameter.empty)
|
||||
self.assertEqual(param.kind, inspect.Parameter.POSITIONAL_OR_KEYWORD)
|
||||
param = next(params)
|
||||
self.assertEqual(param.name, 'k')
|
||||
self.assertIs (param.annotation, F)
|
||||
self.assertIs (param.annotation, 'F')
|
||||
# Don't test for the default, since it's set to MISSING.
|
||||
self.assertEqual(param.kind, inspect.Parameter.POSITIONAL_OR_KEYWORD)
|
||||
param = next(params)
|
||||
self.assertEqual(param.name, 'l')
|
||||
self.assertIs (param.annotation, float)
|
||||
self.assertIs (param.annotation, 'float')
|
||||
# Don't test for the default, since it's set to MISSING.
|
||||
self.assertEqual(param.kind, inspect.Parameter.POSITIONAL_OR_KEYWORD)
|
||||
self.assertRaises(StopIteration, next, params)
|
||||
|
@ -2806,13 +2807,10 @@ class TestDescriptors(unittest.TestCase):
|
|||
|
||||
class TestStringAnnotations(unittest.TestCase):
|
||||
def test_classvar(self):
|
||||
# Some expressions recognized as ClassVar really aren't. But
|
||||
# if you're using string annotations, it's not an exact
|
||||
# science.
|
||||
# These tests assume that both "import typing" and "from
|
||||
# typing import *" have been run in this file.
|
||||
for typestr in ('ClassVar[int]',
|
||||
'ClassVar [int]'
|
||||
'ClassVar [int]',
|
||||
' ClassVar [int]',
|
||||
'ClassVar',
|
||||
' ClassVar ',
|
||||
|
@ -2823,17 +2821,15 @@ class TestStringAnnotations(unittest.TestCase):
|
|||
'typing. ClassVar[str]',
|
||||
'typing.ClassVar [str]',
|
||||
'typing.ClassVar [ str]',
|
||||
|
||||
# Double stringified
|
||||
'"typing.ClassVar[int]"',
|
||||
# Not syntactically valid, but these will
|
||||
# be treated as ClassVars.
|
||||
# be treated as ClassVars.
|
||||
'typing.ClassVar.[int]',
|
||||
'typing.ClassVar+',
|
||||
):
|
||||
with self.subTest(typestr=typestr):
|
||||
@dataclass
|
||||
class C:
|
||||
x: typestr
|
||||
|
||||
C = dataclass(type("C", (), {"__annotations__": {"x": typestr}}))
|
||||
# x is a ClassVar, so C() takes no args.
|
||||
C()
|
||||
|
||||
|
@ -2854,9 +2850,7 @@ class TestStringAnnotations(unittest.TestCase):
|
|||
'typingxClassVar[str]',
|
||||
):
|
||||
with self.subTest(typestr=typestr):
|
||||
@dataclass
|
||||
class C:
|
||||
x: typestr
|
||||
C = dataclass(type("C", (), {"__annotations__": {"x": typestr}}))
|
||||
|
||||
# x is not a ClassVar, so C() takes one arg.
|
||||
self.assertEqual(C(10).x, 10)
|
||||
|
@ -2876,16 +2870,16 @@ class TestStringAnnotations(unittest.TestCase):
|
|||
'dataclasses. InitVar[str]',
|
||||
'dataclasses.InitVar [str]',
|
||||
'dataclasses.InitVar [ str]',
|
||||
|
||||
# Double stringified
|
||||
'"dataclasses.InitVar[int]"',
|
||||
# Not syntactically valid, but these will
|
||||
# be treated as InitVars.
|
||||
'dataclasses.InitVar.[int]',
|
||||
'dataclasses.InitVar+',
|
||||
):
|
||||
with self.subTest(typestr=typestr):
|
||||
@dataclass
|
||||
class C:
|
||||
x: typestr
|
||||
C = dataclass(type("C", (), {"__annotations__": {"x": typestr}}))
|
||||
|
||||
|
||||
# x is an InitVar, so doesn't create a member.
|
||||
with self.assertRaisesRegex(AttributeError,
|
||||
|
@ -2899,30 +2893,22 @@ class TestStringAnnotations(unittest.TestCase):
|
|||
'typing.xInitVar[int]',
|
||||
):
|
||||
with self.subTest(typestr=typestr):
|
||||
@dataclass
|
||||
class C:
|
||||
x: typestr
|
||||
C = dataclass(type("C", (), {"__annotations__": {"x": typestr}}))
|
||||
|
||||
# x is not an InitVar, so there will be a member x.
|
||||
self.assertEqual(C(10).x, 10)
|
||||
|
||||
def test_classvar_module_level_import(self):
|
||||
from test import dataclass_module_1
|
||||
from test import dataclass_module_1_str
|
||||
from test import dataclass_module_2
|
||||
from test import dataclass_module_2_str
|
||||
|
||||
for m in (dataclass_module_1, dataclass_module_1_str,
|
||||
dataclass_module_2, dataclass_module_2_str,
|
||||
):
|
||||
for m in (dataclass_module_1,
|
||||
dataclass_module_2):
|
||||
with self.subTest(m=m):
|
||||
# There's a difference in how the ClassVars are
|
||||
# interpreted when using string annotations or
|
||||
# not. See the imported modules for details.
|
||||
if m.USING_STRINGS:
|
||||
c = m.CV(10)
|
||||
else:
|
||||
c = m.CV()
|
||||
c = m.CV(10)
|
||||
self.assertEqual(c.cv0, 20)
|
||||
|
||||
|
||||
|
@ -2938,14 +2924,9 @@ class TestStringAnnotations(unittest.TestCase):
|
|||
# not an instance field.
|
||||
getattr(c, field_name)
|
||||
|
||||
if m.USING_STRINGS:
|
||||
# iv4 is interpreted as a normal field.
|
||||
self.assertIn('not_iv4', c.__dict__)
|
||||
self.assertEqual(c.not_iv4, 4)
|
||||
else:
|
||||
# iv4 is interpreted as an InitVar, so it
|
||||
# won't exist on the instance.
|
||||
self.assertNotIn('not_iv4', c.__dict__)
|
||||
# iv4 is interpreted as a normal field.
|
||||
self.assertIn('not_iv4', c.__dict__)
|
||||
self.assertEqual(c.not_iv4, 4)
|
||||
|
||||
def test_text_annotations(self):
|
||||
from test import dataclass_textanno
|
||||
|
|
|
@ -227,28 +227,26 @@ dis_annot_stmt_str = """\
|
|||
2 0 SETUP_ANNOTATIONS
|
||||
2 LOAD_CONST 0 (1)
|
||||
4 STORE_NAME 0 (x)
|
||||
6 LOAD_NAME 1 (int)
|
||||
8 LOAD_NAME 2 (__annotations__)
|
||||
10 LOAD_CONST 1 ('x')
|
||||
6 LOAD_CONST 1 ('int')
|
||||
8 LOAD_NAME 1 (__annotations__)
|
||||
10 LOAD_CONST 2 ('x')
|
||||
12 STORE_SUBSCR
|
||||
|
||||
3 14 LOAD_NAME 3 (fun)
|
||||
16 LOAD_CONST 0 (1)
|
||||
18 CALL_FUNCTION 1
|
||||
20 LOAD_NAME 2 (__annotations__)
|
||||
22 LOAD_CONST 2 ('y')
|
||||
24 STORE_SUBSCR
|
||||
3 14 LOAD_CONST 3 ('fun(1)')
|
||||
16 LOAD_NAME 1 (__annotations__)
|
||||
18 LOAD_CONST 4 ('y')
|
||||
20 STORE_SUBSCR
|
||||
|
||||
4 26 LOAD_CONST 0 (1)
|
||||
28 LOAD_NAME 4 (lst)
|
||||
30 LOAD_NAME 3 (fun)
|
||||
32 LOAD_CONST 3 (0)
|
||||
34 CALL_FUNCTION 1
|
||||
36 STORE_SUBSCR
|
||||
38 LOAD_NAME 1 (int)
|
||||
40 POP_TOP
|
||||
42 LOAD_CONST 4 (None)
|
||||
44 RETURN_VALUE
|
||||
4 22 LOAD_CONST 0 (1)
|
||||
24 LOAD_NAME 2 (lst)
|
||||
26 LOAD_NAME 3 (fun)
|
||||
28 LOAD_CONST 5 (0)
|
||||
30 CALL_FUNCTION 1
|
||||
32 STORE_SUBSCR
|
||||
34 LOAD_NAME 4 (int)
|
||||
36 POP_TOP
|
||||
38 LOAD_CONST 6 (None)
|
||||
40 RETURN_VALUE
|
||||
"""
|
||||
|
||||
compound_stmt_str = """\
|
||||
|
|
|
@ -618,7 +618,7 @@ class TestUpdateWrapper(unittest.TestCase):
|
|||
|
||||
|
||||
def _default_update(self):
|
||||
def f(a:'This is a new annotation'):
|
||||
def f(a: int):
|
||||
"""This is a test"""
|
||||
pass
|
||||
f.attr = 'This is also a test'
|
||||
|
@ -635,7 +635,7 @@ class TestUpdateWrapper(unittest.TestCase):
|
|||
self.assertEqual(wrapper.__name__, 'f')
|
||||
self.assertEqual(wrapper.__qualname__, f.__qualname__)
|
||||
self.assertEqual(wrapper.attr, 'This is also a test')
|
||||
self.assertEqual(wrapper.__annotations__['a'], 'This is a new annotation')
|
||||
self.assertEqual(wrapper.__annotations__['a'], 'int')
|
||||
self.assertNotIn('b', wrapper.__annotations__)
|
||||
|
||||
@unittest.skipIf(sys.flags.optimize >= 2,
|
||||
|
|
|
@ -362,7 +362,7 @@ class GrammarTests(unittest.TestCase):
|
|||
z = 2
|
||||
def __init__(self, x):
|
||||
self.x: int = x
|
||||
self.assertEqual(C.__annotations__, {'_C__foo': int, 's': str})
|
||||
self.assertEqual(C.__annotations__, {'_C__foo': 'int', 's': 'str'})
|
||||
with self.assertRaises(NameError):
|
||||
class CBad:
|
||||
no_such_name_defined.attr: int = 0
|
||||
|
@ -378,15 +378,15 @@ class GrammarTests(unittest.TestCase):
|
|||
return {'__annotations__': CNS()}
|
||||
class CC(metaclass=CMeta):
|
||||
XX: 'ANNOT'
|
||||
self.assertEqual(CC.__annotations__['xx'], 'ANNOT')
|
||||
self.assertEqual(CC.__annotations__['xx'], repr('ANNOT'))
|
||||
|
||||
def test_var_annot_module_semantics(self):
|
||||
with self.assertRaises(AttributeError):
|
||||
print(test.__annotations__)
|
||||
self.assertEqual(ann_module.__annotations__,
|
||||
{1: 2, 'x': int, 'y': str, 'f': typing.Tuple[int, int]})
|
||||
{1: 2, 'x': 'int', 'y': 'str', 'f': 'Tuple[int, int]'})
|
||||
self.assertEqual(ann_module.M.__annotations__,
|
||||
{'123': 123, 'o': type})
|
||||
{'123': 123, 'o': 'type'})
|
||||
self.assertEqual(ann_module2.__annotations__, {})
|
||||
|
||||
def test_var_annot_in_module(self):
|
||||
|
@ -405,7 +405,7 @@ class GrammarTests(unittest.TestCase):
|
|||
exec("'docstring'\n"
|
||||
"__annotations__[1] = 2\n"
|
||||
"x: int = 5\n", gns, lns)
|
||||
self.assertEqual(lns["__annotations__"], {1: 2, 'x': int})
|
||||
self.assertEqual(lns["__annotations__"], {1: 2, 'x': 'int'})
|
||||
with self.assertRaises(KeyError):
|
||||
gns['__annotations__']
|
||||
|
||||
|
@ -413,8 +413,8 @@ class GrammarTests(unittest.TestCase):
|
|||
# tests with custom locals() and __annotations__
|
||||
ns = {'__annotations__': CNS()}
|
||||
exec('X: int; Z: str = "Z"; (w): complex = 1j', ns)
|
||||
self.assertEqual(ns['__annotations__']['x'], int)
|
||||
self.assertEqual(ns['__annotations__']['z'], str)
|
||||
self.assertEqual(ns['__annotations__']['x'], 'int')
|
||||
self.assertEqual(ns['__annotations__']['z'], 'str')
|
||||
with self.assertRaises(KeyError):
|
||||
ns['__annotations__']['w']
|
||||
nonloc_ns = {}
|
||||
|
@ -428,7 +428,7 @@ class GrammarTests(unittest.TestCase):
|
|||
def __getitem__(self, item):
|
||||
return self._dct[item]
|
||||
exec('x: int = 1', {}, CNS2())
|
||||
self.assertEqual(nonloc_ns['__annotations__']['x'], int)
|
||||
self.assertEqual(nonloc_ns['__annotations__']['x'], 'int')
|
||||
|
||||
def test_var_annot_refleak(self):
|
||||
# complex case: custom locals plus custom __annotations__
|
||||
|
@ -445,7 +445,7 @@ class GrammarTests(unittest.TestCase):
|
|||
def __getitem__(self, item):
|
||||
return self._dct[item]
|
||||
exec('X: str', {}, CNS2())
|
||||
self.assertEqual(nonloc_ns['__annotations__']['x'], str)
|
||||
self.assertEqual(nonloc_ns['__annotations__']['x'], 'str')
|
||||
|
||||
def test_var_annot_rhs(self):
|
||||
ns = {}
|
||||
|
@ -625,50 +625,46 @@ class GrammarTests(unittest.TestCase):
|
|||
|
||||
# argument annotation tests
|
||||
def f(x) -> list: pass
|
||||
self.assertEqual(f.__annotations__, {'return': list})
|
||||
self.assertEqual(f.__annotations__, {'return': 'list'})
|
||||
def f(x: int): pass
|
||||
self.assertEqual(f.__annotations__, {'x': int})
|
||||
self.assertEqual(f.__annotations__, {'x': 'int'})
|
||||
def f(x: int, /): pass
|
||||
self.assertEqual(f.__annotations__, {'x': int})
|
||||
self.assertEqual(f.__annotations__, {'x': 'int'})
|
||||
def f(x: int = 34, /): pass
|
||||
self.assertEqual(f.__annotations__, {'x': int})
|
||||
self.assertEqual(f.__annotations__, {'x': 'int'})
|
||||
def f(*x: str): pass
|
||||
self.assertEqual(f.__annotations__, {'x': str})
|
||||
self.assertEqual(f.__annotations__, {'x': 'str'})
|
||||
def f(**x: float): pass
|
||||
self.assertEqual(f.__annotations__, {'x': float})
|
||||
def f(x, y: 1+2): pass
|
||||
self.assertEqual(f.__annotations__, {'y': 3})
|
||||
def f(x, y: 1+2, /): pass
|
||||
self.assertEqual(f.__annotations__, {'y': 3})
|
||||
self.assertEqual(f.__annotations__, {'x': 'float'})
|
||||
def f(a, b: 1, c: 2, d): pass
|
||||
self.assertEqual(f.__annotations__, {'b': 1, 'c': 2})
|
||||
self.assertEqual(f.__annotations__, {'b': '1', 'c': '2'})
|
||||
def f(a, b: 1, /, c: 2, d): pass
|
||||
self.assertEqual(f.__annotations__, {'b': 1, 'c': 2})
|
||||
self.assertEqual(f.__annotations__, {'b': '1', 'c': '2'})
|
||||
def f(a, b: 1, c: 2, d, e: 3 = 4, f=5, *g: 6): pass
|
||||
self.assertEqual(f.__annotations__,
|
||||
{'b': 1, 'c': 2, 'e': 3, 'g': 6})
|
||||
{'b': '1', 'c': '2', 'e': '3', 'g': '6'})
|
||||
def f(a, b: 1, c: 2, d, e: 3 = 4, f=5, *g: 6, h: 7, i=8, j: 9 = 10,
|
||||
**k: 11) -> 12: pass
|
||||
self.assertEqual(f.__annotations__,
|
||||
{'b': 1, 'c': 2, 'e': 3, 'g': 6, 'h': 7, 'j': 9,
|
||||
'k': 11, 'return': 12})
|
||||
{'b': '1', 'c': '2', 'e': '3', 'g': '6', 'h': '7', 'j': '9',
|
||||
'k': '11', 'return': '12'})
|
||||
def f(a, b: 1, c: 2, d, e: 3 = 4, f: int = 5, /, *g: 6, h: 7, i=8, j: 9 = 10,
|
||||
**k: 11) -> 12: pass
|
||||
self.assertEqual(f.__annotations__,
|
||||
{'b': 1, 'c': 2, 'e': 3, 'f': int, 'g': 6, 'h': 7, 'j': 9,
|
||||
'k': 11, 'return': 12})
|
||||
{'b': '1', 'c': '2', 'e': '3', 'f': 'int', 'g': '6', 'h': '7', 'j': '9',
|
||||
'k': '11', 'return': '12'})
|
||||
# Check for issue #20625 -- annotations mangling
|
||||
class Spam:
|
||||
def f(self, *, __kw: 1):
|
||||
pass
|
||||
class Ham(Spam): pass
|
||||
self.assertEqual(Spam.f.__annotations__, {'_Spam__kw': 1})
|
||||
self.assertEqual(Ham.f.__annotations__, {'_Spam__kw': 1})
|
||||
self.assertEqual(Spam.f.__annotations__, {'_Spam__kw': '1'})
|
||||
self.assertEqual(Ham.f.__annotations__, {'_Spam__kw': '1'})
|
||||
# Check for SF Bug #1697248 - mixing decorators and a return annotation
|
||||
def null(x): return x
|
||||
@null
|
||||
def f(x) -> list: pass
|
||||
self.assertEqual(f.__annotations__, {'return': list})
|
||||
self.assertEqual(f.__annotations__, {'return': 'list'})
|
||||
|
||||
# Test expressions as decorators (PEP 614):
|
||||
@False or null
|
||||
|
@ -1116,8 +1112,6 @@ class GrammarTests(unittest.TestCase):
|
|||
# Not allowed at class scope
|
||||
check_syntax_error(self, "class foo:yield 1")
|
||||
check_syntax_error(self, "class foo:yield from ()")
|
||||
# Check annotation refleak on SyntaxError
|
||||
check_syntax_error(self, "def g(a:(yield)): pass")
|
||||
|
||||
def test_yield_in_comprehensions(self):
|
||||
# Check yield in comprehensions
|
||||
|
|
|
@ -862,7 +862,7 @@ class TestClassesAndFunctions(unittest.TestCase):
|
|||
|
||||
self.assertFullArgSpecEquals(mod2.annotated, ['arg1'],
|
||||
ann_e={'arg1' : list},
|
||||
formatted='(arg1: list)')
|
||||
formatted="(arg1: list)")
|
||||
self.assertFullArgSpecEquals(mod2.keyword_only_arg, [],
|
||||
kwonlyargs_e=['arg'],
|
||||
formatted='(*, arg)')
|
||||
|
@ -2211,8 +2211,8 @@ class TestSignatureObject(unittest.TestCase):
|
|||
pass
|
||||
self.assertEqual(self.signature(test),
|
||||
((('a', ..., ..., "positional_or_keyword"),
|
||||
('b', ..., 'foo', "positional_or_keyword")),
|
||||
123))
|
||||
('b', ..., repr('foo'), "positional_or_keyword")),
|
||||
'123'))
|
||||
|
||||
def test_signature_on_wkwonly(self):
|
||||
def test(*, a:float, b:str) -> int:
|
||||
|
@ -2227,11 +2227,11 @@ class TestSignatureObject(unittest.TestCase):
|
|||
pass
|
||||
self.assertEqual(self.signature(test),
|
||||
((('a', ..., ..., "positional_or_keyword"),
|
||||
('b', 10, 'foo', "positional_or_keyword"),
|
||||
('args', ..., 'bar', "var_positional"),
|
||||
('spam', ..., 'baz', "keyword_only"),
|
||||
('b', 10, repr('foo'), "positional_or_keyword"),
|
||||
('args', ..., repr('bar'), "var_positional"),
|
||||
('spam', ..., repr('baz'), "keyword_only"),
|
||||
('ham', 123, ..., "keyword_only"),
|
||||
('kwargs', ..., int, "var_keyword")),
|
||||
('kwargs', ..., 'int', "var_keyword")),
|
||||
...))
|
||||
|
||||
def test_signature_without_self(self):
|
||||
|
@ -2640,12 +2640,12 @@ class TestSignatureObject(unittest.TestCase):
|
|||
|
||||
self.assertEqual(self.signature(partial(partial(test, 1))),
|
||||
((('b', ..., ..., "positional_or_keyword"),
|
||||
('c', ..., int, "positional_or_keyword")),
|
||||
42))
|
||||
('c', ..., 'int', "positional_or_keyword")),
|
||||
'42'))
|
||||
|
||||
self.assertEqual(self.signature(partial(partial(test, 1), 2)),
|
||||
((('c', ..., int, "positional_or_keyword"),),
|
||||
42))
|
||||
((('c', ..., 'int', "positional_or_keyword"),),
|
||||
'42'))
|
||||
|
||||
psig = inspect.signature(partial(partial(test, 1), 2))
|
||||
|
||||
|
@ -2764,12 +2764,12 @@ class TestSignatureObject(unittest.TestCase):
|
|||
((('it', ..., ..., 'positional_or_keyword'),
|
||||
('a', ..., ..., 'positional_or_keyword'),
|
||||
('c', 1, ..., 'keyword_only')),
|
||||
'spam'))
|
||||
repr('spam')))
|
||||
|
||||
self.assertEqual(self.signature(Spam().ham),
|
||||
((('a', ..., ..., 'positional_or_keyword'),
|
||||
('c', 1, ..., 'keyword_only')),
|
||||
'spam'))
|
||||
repr('spam')))
|
||||
|
||||
class Spam:
|
||||
def test(self: 'anno', x):
|
||||
|
@ -2778,7 +2778,7 @@ class TestSignatureObject(unittest.TestCase):
|
|||
g = partialmethod(test, 1)
|
||||
|
||||
self.assertEqual(self.signature(Spam.g),
|
||||
((('self', ..., 'anno', 'positional_or_keyword'),),
|
||||
((('self', ..., repr('anno'), 'positional_or_keyword'),),
|
||||
...))
|
||||
|
||||
def test_signature_on_fake_partialmethod(self):
|
||||
|
@ -3116,20 +3116,16 @@ class TestSignatureObject(unittest.TestCase):
|
|||
with self.assertRaisesRegex(TypeError, 'unhashable type'):
|
||||
hash(inspect.signature(foo))
|
||||
|
||||
def foo(a) -> {}: pass
|
||||
with self.assertRaisesRegex(TypeError, 'unhashable type'):
|
||||
hash(inspect.signature(foo))
|
||||
|
||||
def test_signature_str(self):
|
||||
def foo(a:int=1, *, b, c=None, **kwargs) -> 42:
|
||||
pass
|
||||
self.assertEqual(str(inspect.signature(foo)),
|
||||
'(a: int = 1, *, b, c=None, **kwargs) -> 42')
|
||||
'(a: \'int\' = 1, *, b, c=None, **kwargs) -> \'42\'')
|
||||
|
||||
def foo(a:int=1, *args, b, c=None, **kwargs) -> 42:
|
||||
pass
|
||||
self.assertEqual(str(inspect.signature(foo)),
|
||||
'(a: int = 1, *args, b, c=None, **kwargs) -> 42')
|
||||
'(a: \'int\' = 1, *args, b, c=None, **kwargs) -> \'42\'')
|
||||
|
||||
def foo():
|
||||
pass
|
||||
|
@ -3172,8 +3168,8 @@ class TestSignatureObject(unittest.TestCase):
|
|||
self.assertIs(sig.return_annotation, None)
|
||||
sig = sig.replace(return_annotation=sig.empty)
|
||||
self.assertIs(sig.return_annotation, sig.empty)
|
||||
sig = sig.replace(return_annotation=42)
|
||||
self.assertEqual(sig.return_annotation, 42)
|
||||
sig = sig.replace(return_annotation='42')
|
||||
self.assertEqual(sig.return_annotation, '42')
|
||||
self.assertEqual(sig, inspect.signature(test))
|
||||
|
||||
def test_signature_on_mangled_parameters(self):
|
||||
|
@ -3185,8 +3181,8 @@ class TestSignatureObject(unittest.TestCase):
|
|||
|
||||
self.assertEqual(self.signature(Spam.foo),
|
||||
((('self', ..., ..., "positional_or_keyword"),
|
||||
('_Spam__p1', 2, 1, "positional_or_keyword"),
|
||||
('_Spam__p2', 3, 2, "keyword_only")),
|
||||
('_Spam__p1', 2, '1', "positional_or_keyword"),
|
||||
('_Spam__p2', 3, '2', "keyword_only")),
|
||||
...))
|
||||
|
||||
self.assertEqual(self.signature(Spam.foo),
|
||||
|
|
|
@ -39,7 +39,7 @@ class OpcodeTest(unittest.TestCase):
|
|||
def test_use_existing_annotations(self):
|
||||
ns = {'__annotations__': {1: 2}}
|
||||
exec('x: int', ns)
|
||||
self.assertEqual(ns['__annotations__'], {'x': int, 1: 2})
|
||||
self.assertEqual(ns['__annotations__'], {'x': 'int', 1: 2})
|
||||
|
||||
def test_do_not_recreate_annotations(self):
|
||||
# Don't rely on the existence of the '__annotations__' global.
|
||||
|
|
|
@ -302,14 +302,14 @@ class PositionalOnlyTestCase(unittest.TestCase):
|
|||
def f(x: int, /): ...
|
||||
return f
|
||||
|
||||
assert inner_has_pos_only().__annotations__ == {'x': int}
|
||||
assert inner_has_pos_only().__annotations__ == {'x': 'int'}
|
||||
|
||||
class Something:
|
||||
def method(self):
|
||||
def f(x: int, /): ...
|
||||
return f
|
||||
|
||||
assert Something().method().__annotations__ == {'x': int}
|
||||
assert Something().method().__annotations__ == {'x': 'int'}
|
||||
|
||||
def multiple_levels():
|
||||
def inner_has_pos_only():
|
||||
|
@ -317,7 +317,7 @@ class PositionalOnlyTestCase(unittest.TestCase):
|
|||
return f
|
||||
return inner_has_pos_only()
|
||||
|
||||
assert multiple_levels().__annotations__ == {'x': int}
|
||||
assert multiple_levels().__annotations__ == {'x': 'int'}
|
||||
|
||||
def test_same_keyword_as_positional_with_kwargs(self):
|
||||
def f(something,/,**kwargs):
|
||||
|
@ -429,17 +429,6 @@ class PositionalOnlyTestCase(unittest.TestCase):
|
|||
|
||||
self.assertEqual(C().method(), sentinel)
|
||||
|
||||
def test_annotations_constant_fold(self):
|
||||
def g():
|
||||
def f(x: not (int is int), /): ...
|
||||
|
||||
# without constant folding we end up with
|
||||
# COMPARE_OP(is), IS_OP (0)
|
||||
# with constant folding we should expect a IS_OP (1)
|
||||
codes = [(i.opname, i.argval) for i in dis.get_instructions(g)]
|
||||
self.assertNotIn(('UNARY_NOT', None), codes)
|
||||
self.assertIn(('IS_OP', 1), codes)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
@ -81,7 +81,7 @@ CLASSES
|
|||
|\x20\x20
|
||||
| NO_MEANING = 'eggs'
|
||||
|\x20\x20
|
||||
| __annotations__ = {'NO_MEANING': <class 'str'>}
|
||||
| __annotations__ = {'NO_MEANING': 'str'}
|
||||
\x20\x20\x20\x20
|
||||
class C(builtins.object)
|
||||
| Methods defined here:
|
||||
|
@ -194,7 +194,7 @@ Data descriptors defined here:<br>
|
|||
Data and other attributes defined here:<br>
|
||||
<dl><dt><strong>NO_MEANING</strong> = 'eggs'</dl>
|
||||
|
||||
<dl><dt><strong>__annotations__</strong> = {'NO_MEANING': <class 'str'>}</dl>
|
||||
<dl><dt><strong>__annotations__</strong> = {'NO_MEANING': 'str'}</dl>
|
||||
|
||||
</td></tr></table> <p>
|
||||
<table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||
|
|
|
@ -752,14 +752,6 @@ Corner-cases that used to fail to raise the correct error:
|
|||
Traceback (most recent call last):
|
||||
SyntaxError: cannot assign to __debug__
|
||||
|
||||
>>> def f(*args:(lambda __debug__:0)): pass
|
||||
Traceback (most recent call last):
|
||||
SyntaxError: cannot assign to __debug__
|
||||
|
||||
>>> def f(**kwargs:(lambda __debug__:0)): pass
|
||||
Traceback (most recent call last):
|
||||
SyntaxError: cannot assign to __debug__
|
||||
|
||||
>>> with (lambda *:0): pass
|
||||
Traceback (most recent call last):
|
||||
SyntaxError: named arguments must follow bare *
|
||||
|
|
|
@ -671,8 +671,8 @@ class TypesTests(unittest.TestCase):
|
|||
ForwardBefore = 'Forward' | T
|
||||
def forward_after(x: ForwardAfter[int]) -> None: ...
|
||||
def forward_before(x: ForwardBefore[int]) -> None: ...
|
||||
assert typing.get_args(typing.get_type_hints(forward_after)['x']) == (int, Forward)
|
||||
assert typing.get_args(typing.get_type_hints(forward_before)['x']) == (int, Forward)
|
||||
assert typing.get_args(typing.get_type_hints(forward_after, localns=locals())['x']) == (int, Forward)
|
||||
assert typing.get_args(typing.get_type_hints(forward_before, localns=locals())['x']) == (int, Forward)
|
||||
|
||||
def test_or_type_operator_with_Protocol(self):
|
||||
class Proto(typing.Protocol):
|
||||
|
|
|
@ -349,7 +349,7 @@ class UnionTests(BaseTestCase):
|
|||
def test_no_eval_union(self):
|
||||
u = Union[int, str]
|
||||
def f(x: u): ...
|
||||
self.assertIs(get_type_hints(f)['x'], u)
|
||||
self.assertIs(get_type_hints(f, globals(), locals())['x'], u)
|
||||
|
||||
def test_function_repr_union(self):
|
||||
def fun() -> int: ...
|
||||
|
@ -2849,11 +2849,11 @@ class GetTypeHintTests(BaseTestCase):
|
|||
self.assertEqual(gth(HasForeignBaseClass),
|
||||
{'some_xrepr': XRepr, 'other_a': mod_generics_cache.A,
|
||||
'some_b': mod_generics_cache.B})
|
||||
self.assertEqual(gth(XRepr.__new__),
|
||||
self.assertEqual(gth(XRepr),
|
||||
{'x': int, 'y': int})
|
||||
self.assertEqual(gth(mod_generics_cache.B),
|
||||
{'my_inner_a1': mod_generics_cache.B.A,
|
||||
'my_inner_a2': mod_generics_cache.B.A,
|
||||
'my_inner_a2': mod_generics_cache.A,
|
||||
'my_outer_a': mod_generics_cache.A})
|
||||
|
||||
def test_respect_no_type_check(self):
|
||||
|
@ -3641,7 +3641,7 @@ class NamedTupleTests(BaseTestCase):
|
|||
self.assertEqual(tim.cool, 9000)
|
||||
self.assertEqual(CoolEmployee.__name__, 'CoolEmployee')
|
||||
self.assertEqual(CoolEmployee._fields, ('name', 'cool'))
|
||||
self.assertEqual(CoolEmployee.__annotations__,
|
||||
self.assertEqual(gth(CoolEmployee),
|
||||
collections.OrderedDict(name=str, cool=int))
|
||||
|
||||
def test_annotation_usage_with_default(self):
|
||||
|
@ -3655,7 +3655,7 @@ class NamedTupleTests(BaseTestCase):
|
|||
|
||||
self.assertEqual(CoolEmployeeWithDefault.__name__, 'CoolEmployeeWithDefault')
|
||||
self.assertEqual(CoolEmployeeWithDefault._fields, ('name', 'cool'))
|
||||
self.assertEqual(CoolEmployeeWithDefault.__annotations__,
|
||||
self.assertEqual(gth(CoolEmployeeWithDefault),
|
||||
dict(name=str, cool=int))
|
||||
self.assertEqual(CoolEmployeeWithDefault._field_defaults, dict(cool=0))
|
||||
|
||||
|
@ -3823,7 +3823,7 @@ class TypedDictTests(BaseTestCase):
|
|||
def test_py36_class_syntax_usage(self):
|
||||
self.assertEqual(LabelPoint2D.__name__, 'LabelPoint2D')
|
||||
self.assertEqual(LabelPoint2D.__module__, __name__)
|
||||
self.assertEqual(LabelPoint2D.__annotations__, {'x': int, 'y': int, 'label': str})
|
||||
self.assertEqual(gth(LabelPoint2D), {'x': int, 'y': int, 'label': str})
|
||||
self.assertEqual(LabelPoint2D.__bases__, (dict,))
|
||||
self.assertEqual(LabelPoint2D.__total__, True)
|
||||
self.assertNotIsSubclass(LabelPoint2D, typing.Sequence)
|
||||
|
@ -3882,11 +3882,11 @@ class TypedDictTests(BaseTestCase):
|
|||
|
||||
assert BaseAnimal.__required_keys__ == frozenset(['name'])
|
||||
assert BaseAnimal.__optional_keys__ == frozenset([])
|
||||
assert BaseAnimal.__annotations__ == {'name': str}
|
||||
assert gth(BaseAnimal) == {'name': str}
|
||||
|
||||
assert Animal.__required_keys__ == frozenset(['name'])
|
||||
assert Animal.__optional_keys__ == frozenset(['tail', 'voice'])
|
||||
assert Animal.__annotations__ == {
|
||||
assert gth(Animal) == {
|
||||
'name': str,
|
||||
'tail': bool,
|
||||
'voice': str,
|
||||
|
@ -3894,7 +3894,7 @@ class TypedDictTests(BaseTestCase):
|
|||
|
||||
assert Cat.__required_keys__ == frozenset(['name', 'fur_color'])
|
||||
assert Cat.__optional_keys__ == frozenset(['tail', 'voice'])
|
||||
assert Cat.__annotations__ == {
|
||||
assert gth(Cat) == {
|
||||
'fur_color': str,
|
||||
'name': str,
|
||||
'tail': bool,
|
||||
|
@ -3915,7 +3915,7 @@ class IOTests(BaseTestCase):
|
|||
def stuff(a: IO) -> AnyStr:
|
||||
return a.readline()
|
||||
|
||||
a = stuff.__annotations__['a']
|
||||
a = gth(stuff)['a']
|
||||
self.assertEqual(a.__parameters__, (AnyStr,))
|
||||
|
||||
def test_textio(self):
|
||||
|
@ -3923,7 +3923,7 @@ class IOTests(BaseTestCase):
|
|||
def stuff(a: TextIO) -> str:
|
||||
return a.readline()
|
||||
|
||||
a = stuff.__annotations__['a']
|
||||
a = gth(stuff)['a']
|
||||
self.assertEqual(a.__parameters__, ())
|
||||
|
||||
def test_binaryio(self):
|
||||
|
@ -3931,7 +3931,7 @@ class IOTests(BaseTestCase):
|
|||
def stuff(a: BinaryIO) -> bytes:
|
||||
return a.readline()
|
||||
|
||||
a = stuff.__annotations__['a']
|
||||
a = gth(stuff)['a']
|
||||
self.assertEqual(a.__parameters__, ())
|
||||
|
||||
def test_io_submodule(self):
|
||||
|
|
|
@ -18,6 +18,7 @@ At large scale, the structure of the module is following:
|
|||
"""
|
||||
|
||||
from abc import abstractmethod, ABCMeta
|
||||
import ast
|
||||
import collections
|
||||
import collections.abc
|
||||
import contextlib
|
||||
|
@ -469,6 +470,13 @@ class ForwardRef(_Final, _root=True):
|
|||
def __init__(self, arg, is_argument=True):
|
||||
if not isinstance(arg, str):
|
||||
raise TypeError(f"Forward reference must be a string -- got {arg!r}")
|
||||
|
||||
# Double-stringified forward references is a result of activating
|
||||
# the 'annotations' future by default. This way, we eliminate them in
|
||||
# the runtime.
|
||||
if arg.startswith(("'", '\"')) and arg.endswith(("'", '"')):
|
||||
arg = arg[1:-1]
|
||||
|
||||
try:
|
||||
code = compile(arg, '<string>', 'eval')
|
||||
except SyntaxError:
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
Enable ``from __future__ import annotations`` (:pep:`563`) by default.
|
||||
The values found in :attr:`__annotations__` dicts are now strings, e.g.
|
||||
``{"x": "int"}`` instead of ``{"x": int}``.
|
|
@ -392,7 +392,6 @@ static int astfold_expr(expr_ty node_, PyArena *ctx_, _PyASTOptimizeState *state
|
|||
static int astfold_arguments(arguments_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
|
||||
static int astfold_comprehension(comprehension_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
|
||||
static int astfold_keyword(keyword_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
|
||||
static int astfold_arg(arg_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
|
||||
static int astfold_withitem(withitem_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
|
||||
static int astfold_excepthandler(excepthandler_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
|
||||
#define CALL(FUNC, TYPE, ARG) \
|
||||
|
@ -595,25 +594,11 @@ astfold_comprehension(comprehension_ty node_, PyArena *ctx_, _PyASTOptimizeState
|
|||
static int
|
||||
astfold_arguments(arguments_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
|
||||
{
|
||||
CALL_SEQ(astfold_arg, arg, node_->posonlyargs);
|
||||
CALL_SEQ(astfold_arg, arg, node_->args);
|
||||
CALL_OPT(astfold_arg, arg_ty, node_->vararg);
|
||||
CALL_SEQ(astfold_arg, arg, node_->kwonlyargs);
|
||||
CALL_SEQ(astfold_expr, expr, node_->kw_defaults);
|
||||
CALL_OPT(astfold_arg, arg_ty, node_->kwarg);
|
||||
CALL_SEQ(astfold_expr, expr, node_->defaults);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
astfold_arg(arg_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
|
||||
{
|
||||
if (!(state->ff_features & CO_FUTURE_ANNOTATIONS)) {
|
||||
CALL_OPT(astfold_expr, expr_ty, node_->annotation);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
astfold_stmt(stmt_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
|
||||
{
|
||||
|
@ -622,17 +607,11 @@ astfold_stmt(stmt_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
|
|||
CALL(astfold_arguments, arguments_ty, node_->v.FunctionDef.args);
|
||||
CALL(astfold_body, asdl_seq, node_->v.FunctionDef.body);
|
||||
CALL_SEQ(astfold_expr, expr, node_->v.FunctionDef.decorator_list);
|
||||
if (!(state->ff_features & CO_FUTURE_ANNOTATIONS)) {
|
||||
CALL_OPT(astfold_expr, expr_ty, node_->v.FunctionDef.returns);
|
||||
}
|
||||
break;
|
||||
case AsyncFunctionDef_kind:
|
||||
CALL(astfold_arguments, arguments_ty, node_->v.AsyncFunctionDef.args);
|
||||
CALL(astfold_body, asdl_seq, node_->v.AsyncFunctionDef.body);
|
||||
CALL_SEQ(astfold_expr, expr, node_->v.AsyncFunctionDef.decorator_list);
|
||||
if (!(state->ff_features & CO_FUTURE_ANNOTATIONS)) {
|
||||
CALL_OPT(astfold_expr, expr_ty, node_->v.AsyncFunctionDef.returns);
|
||||
}
|
||||
break;
|
||||
case ClassDef_kind:
|
||||
CALL_SEQ(astfold_expr, expr, node_->v.ClassDef.bases);
|
||||
|
@ -656,9 +635,6 @@ astfold_stmt(stmt_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
|
|||
break;
|
||||
case AnnAssign_kind:
|
||||
CALL(astfold_expr, expr_ty, node_->v.AnnAssign.target);
|
||||
if (!(state->ff_features & CO_FUTURE_ANNOTATIONS)) {
|
||||
CALL(astfold_expr, expr_ty, node_->v.AnnAssign.annotation);
|
||||
}
|
||||
CALL_OPT(astfold_expr, expr_ty, node_->v.AnnAssign.value);
|
||||
break;
|
||||
case For_kind:
|
||||
|
|
|
@ -2026,12 +2026,7 @@ compiler_visit_argannotation(struct compiler *c, identifier id,
|
|||
{
|
||||
if (annotation) {
|
||||
PyObject *mangled;
|
||||
if (c->c_future->ff_features & CO_FUTURE_ANNOTATIONS) {
|
||||
VISIT(c, annexpr, annotation)
|
||||
}
|
||||
else {
|
||||
VISIT(c, expr, annotation);
|
||||
}
|
||||
VISIT(c, annexpr, annotation);
|
||||
mangled = _Py_Mangle(c->u->u_private, id);
|
||||
if (!mangled)
|
||||
return 0;
|
||||
|
@ -5261,12 +5256,7 @@ compiler_annassign(struct compiler *c, stmt_ty s)
|
|||
if (s->v.AnnAssign.simple &&
|
||||
(c->u->u_scope_type == COMPILER_SCOPE_MODULE ||
|
||||
c->u->u_scope_type == COMPILER_SCOPE_CLASS)) {
|
||||
if (c->c_future->ff_features & CO_FUTURE_ANNOTATIONS) {
|
||||
VISIT(c, annexpr, s->v.AnnAssign.annotation)
|
||||
}
|
||||
else {
|
||||
VISIT(c, expr, s->v.AnnAssign.annotation);
|
||||
}
|
||||
VISIT(c, annexpr, s->v.AnnAssign.annotation);
|
||||
ADDOP_NAME(c, LOAD_NAME, __annotations__, names);
|
||||
mangled = _Py_Mangle(c->u->u_private, targ->v.Name.id);
|
||||
ADDOP_LOAD_CONST_NEW(c, mangled);
|
||||
|
|
|
@ -41,7 +41,7 @@ future_check_features(PyFutureFeatures *ff, stmt_ty s, PyObject *filename)
|
|||
} else if (strcmp(feature, FUTURE_GENERATOR_STOP) == 0) {
|
||||
continue;
|
||||
} else if (strcmp(feature, FUTURE_ANNOTATIONS) == 0) {
|
||||
ff->ff_features |= CO_FUTURE_ANNOTATIONS;
|
||||
continue;
|
||||
} else if (strcmp(feature, "braces") == 0) {
|
||||
PyErr_SetString(PyExc_SyntaxError,
|
||||
"not a chance");
|
||||
|
|
Loading…
Reference in New Issue