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:
Batuhan Taskaya 2020-10-06 23:03:02 +03:00 committed by GitHub
parent bef7d299eb
commit 044a1048ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 403 additions and 299 deletions

View File

@ -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

View File

@ -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`.)

View File

@ -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)

View File

@ -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__

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -1,5 +1,3 @@
from __future__ import annotations
import dataclasses

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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 = """\

View File

@ -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,

View File

@ -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

View File

@ -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),

View File

@ -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.

View File

@ -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()

View File

@ -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': &lt;class 'str'&gt;}</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">

View File

@ -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 *

View File

@ -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):

View File

@ -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):

View File

@ -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:

View File

@ -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}``.

View File

@ -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:

View File

@ -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);

View File

@ -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");