Skip to content

Commit 6209fa0

Browse files
committed
Add documentation, tests for annotations
1 parent f217d48 commit 6209fa0

File tree

3 files changed

+233
-5
lines changed

3 files changed

+233
-5
lines changed

Doc/reference/compound_stmts.rst

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -612,11 +612,10 @@ the form "``-> expression``" after the parameter list. These annotations can be
612612
any valid Python expression. The presence of annotations does not change the
613613
semantics of a function. The annotation values are available as values of
614614
a dictionary keyed by the parameters' names in the :attr:`__annotations__`
615-
attribute of the function object. If the ``annotations`` import from
616-
:mod:`__future__` is used, annotations are preserved as strings at runtime which
617-
enables postponed evaluation. Otherwise, they are evaluated when the function
618-
definition is executed. In this case annotations may be evaluated in
619-
a different order than they appear in the source code.
615+
attribute of the function object. Used annnotations are preserved as strings at
616+
runtime which enables postponed evaluation. In this case annotations may be
617+
evaluated in In this case annotations may be evaluated in a different order
618+
than they appear in the source code.
620619

621620
.. index:: pair: lambda; expression
622621

Lib/test/test_annotations.py

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
import unittest
2+
from textwrap import dedent
3+
import sys
4+
5+
class AnnotationsFutureTestCase(unittest.TestCase):
6+
template = dedent(
7+
"""
8+
from __future__ import annotations
9+
def f() -> {ann}:
10+
...
11+
def g(arg: {ann}) -> None:
12+
...
13+
async def f2() -> {ann}:
14+
...
15+
async def g2(arg: {ann}) -> None:
16+
...
17+
var: {ann}
18+
var2: {ann} = None
19+
"""
20+
)
21+
22+
def getActual(self, annotation):
23+
scope = {}
24+
exec(self.template.format(ann=annotation), {}, scope)
25+
func_ret_ann = scope['f'].__annotations__['return']
26+
func_arg_ann = scope['g'].__annotations__['arg']
27+
async_func_ret_ann = scope['f2'].__annotations__['return']
28+
async_func_arg_ann = scope['g2'].__annotations__['arg']
29+
var_ann1 = scope['__annotations__']['var']
30+
var_ann2 = scope['__annotations__']['var2']
31+
self.assertEqual(func_ret_ann, func_arg_ann)
32+
self.assertEqual(func_ret_ann, async_func_ret_ann)
33+
self.assertEqual(func_ret_ann, async_func_arg_ann)
34+
self.assertEqual(func_ret_ann, var_ann1)
35+
self.assertEqual(func_ret_ann, var_ann2)
36+
return func_ret_ann
37+
38+
def assertAnnotationEqual(
39+
self, annotation, expected=None, drop_parens=False, is_tuple=False,
40+
):
41+
actual = self.getActual(annotation)
42+
if expected is None:
43+
expected = annotation if not is_tuple else annotation[1:-1]
44+
if drop_parens:
45+
self.assertNotEqual(actual, expected)
46+
actual = actual.replace("(", "").replace(")", "")
47+
48+
self.assertEqual(actual, expected)
49+
50+
def test_annotations(self):
51+
eq = self.assertAnnotationEqual
52+
eq('...')
53+
eq("'some_string'")
54+
eq("u'some_string'")
55+
eq("b'\\xa3'")
56+
eq('Name')
57+
eq('None')
58+
eq('True')
59+
eq('False')
60+
eq('1')
61+
eq('1.0')
62+
eq('1j')
63+
eq('True or False')
64+
eq('True or False or None')
65+
eq('True and False')
66+
eq('True and False and None')
67+
eq('Name1 and Name2 or Name3')
68+
eq('Name1 and (Name2 or Name3)')
69+
eq('Name1 or Name2 and Name3')
70+
eq('(Name1 or Name2) and Name3')
71+
eq('Name1 and Name2 or Name3 and Name4')
72+
eq('Name1 or Name2 and Name3 or Name4')
73+
eq('a + b + (c + d)')
74+
eq('a * b * (c * d)')
75+
eq('(a ** b) ** c ** d')
76+
eq('v1 << 2')
77+
eq('1 >> v2')
78+
eq('1 % finished')
79+
eq('1 + v2 - v3 * 4 ^ 5 ** v6 / 7 // 8')
80+
eq('not great')
81+
eq('not not great')
82+
eq('~great')
83+
eq('+value')
84+
eq('++value')
85+
eq('-1')
86+
eq('~int and not v1 ^ 123 + v2 | True')
87+
eq('a + (not b)')
88+
eq('lambda: None')
89+
eq('lambda arg: None')
90+
eq('lambda a=True: a')
91+
eq('lambda a, b, c=True: a')
92+
eq("lambda a, b, c=True, *, d=1 << v2, e='str': a")
93+
eq("lambda a, b, c=True, *vararg, d, e='str', **kwargs: a + b")
94+
eq("lambda a, /, b, c=True, *vararg, d, e='str', **kwargs: a + b")
95+
eq('lambda x, /: x')
96+
eq('lambda x=1, /: x')
97+
eq('lambda x, /, y: x + y')
98+
eq('lambda x=1, /, y=2: x + y')
99+
eq('lambda x, /, y=1: x + y')
100+
eq('lambda x, /, y=1, *, z=3: x + y + z')
101+
eq('lambda x=1, /, y=2, *, z=3: x + y + z')
102+
eq('lambda x=1, /, y=2, *, z: x + y + z')
103+
eq('lambda x=1, y=2, z=3, /, w=4, *, l, l2: x + y + z + w + l + l2')
104+
eq('lambda x=1, y=2, z=3, /, w=4, *, l, l2, **kwargs: x + y + z + w + l + l2')
105+
eq('lambda x, /, y=1, *, z: x + y + z')
106+
eq('lambda x: lambda y: x + y')
107+
eq('1 if True else 2')
108+
eq('str or None if int or True else str or bytes or None')
109+
eq('str or None if (1 if True else 2) else str or bytes or None')
110+
eq("0 if not x else 1 if x > 0 else -1")
111+
eq("(1 if x > 0 else -1) if x else 0")
112+
eq("{'2.7': dead, '3.7': long_live or die_hard}")
113+
eq("{'2.7': dead, '3.7': long_live or die_hard, **{'3.6': verygood}}")
114+
eq("{**a, **b, **c}")
115+
eq("{'2.7', '3.6', '3.7', '3.8', '3.9', '4.0' if gilectomy else '3.10'}")
116+
eq("{*a, *b, *c}")
117+
eq("({'a': 'b'}, True or False, +value, 'string', b'bytes') or None")
118+
eq("()")
119+
eq("(a,)")
120+
eq("(a, b)")
121+
eq("(a, b, c)")
122+
eq("(*a, *b, *c)")
123+
eq("[]")
124+
eq("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10 or A, 11 or B, 12 or C]")
125+
eq("[*a, *b, *c]")
126+
eq("{i for i in (1, 2, 3)}")
127+
eq("{i ** 2 for i in (1, 2, 3)}")
128+
eq("{i ** 2 for i, _ in ((1, 'a'), (2, 'b'), (3, 'c'))}")
129+
eq("{i ** 2 + j for i in (1, 2, 3) for j in (1, 2, 3)}")
130+
eq("[i for i in (1, 2, 3)]")
131+
eq("[i ** 2 for i in (1, 2, 3)]")
132+
eq("[i ** 2 for i, _ in ((1, 'a'), (2, 'b'), (3, 'c'))]")
133+
eq("[i ** 2 + j for i in (1, 2, 3) for j in (1, 2, 3)]")
134+
eq("(i for i in (1, 2, 3))")
135+
eq("(i ** 2 for i in (1, 2, 3))")
136+
eq("(i ** 2 for i, _ in ((1, 'a'), (2, 'b'), (3, 'c')))")
137+
eq("(i ** 2 + j for i in (1, 2, 3) for j in (1, 2, 3))")
138+
eq("{i: 0 for i in (1, 2, 3)}")
139+
eq("{i: j for i, j in ((1, 'a'), (2, 'b'), (3, 'c'))}")
140+
eq("[(x, y) for x, y in (a, b)]")
141+
eq("[(x,) for x, in (a,)]")
142+
eq("Python3 > Python2 > COBOL")
143+
eq("Life is Life")
144+
eq("call()")
145+
eq("call(arg)")
146+
eq("call(kwarg='hey')")
147+
eq("call(arg, kwarg='hey')")
148+
eq("call(arg, *args, another, kwarg='hey')")
149+
eq("call(arg, another, kwarg='hey', **kwargs, kwarg2='ho')")
150+
eq("lukasz.langa.pl")
151+
eq("call.me(maybe)")
152+
eq("1 .real")
153+
eq("1.0.real")
154+
eq("....__class__")
155+
eq("list[str]")
156+
eq("dict[str, int]")
157+
eq("set[str,]")
158+
eq("tuple[str, ...]")
159+
eq("tuple[(str, *types)]")
160+
eq("tuple[str, int, (str, int)]")
161+
eq("tuple[(*int, str, str, (str, int))]")
162+
eq("tuple[str, int, float, dict[str, int]]")
163+
eq("slice[0]")
164+
eq("slice[0:1]")
165+
eq("slice[0:1:2]")
166+
eq("slice[:]")
167+
eq("slice[:-1]")
168+
eq("slice[1:]")
169+
eq("slice[::-1]")
170+
eq("slice[:,]")
171+
eq("slice[1:2,]")
172+
eq("slice[1:2:3,]")
173+
eq("slice[1:2, 1]")
174+
eq("slice[1:2, 2, 3]")
175+
eq("slice[()]")
176+
eq("slice[a, b:c, d:e:f]")
177+
eq("slice[(x for x in a)]")
178+
eq('str or None if sys.version_info[0] > (3,) else str or bytes or None')
179+
eq("f'f-string without formatted values is just a string'")
180+
eq("f'{{NOT a formatted value}}'")
181+
eq("f'some f-string with {a} {few():.2f} {formatted.values!r}'")
182+
eq('''f"{f'{nested} inner'} outer"''')
183+
eq("f'space between opening braces: { {a for a in (1, 2, 3)}}'")
184+
eq("f'{(lambda x: x)}'")
185+
eq("f'{(None if a else lambda x: x)}'")
186+
eq("f'{x}'")
187+
eq("f'{x!r}'")
188+
eq("f'{x!a}'")
189+
eq('(yield from outside_of_generator)')
190+
eq('(yield)')
191+
eq('(yield a + b)')
192+
eq('await some.complicated[0].call(with_args=True or 1 is not 1)')
193+
eq('[x for x in (a if b else c)]')
194+
eq('[x for x in a if (b if c else d)]')
195+
eq('f(x for x in a)')
196+
eq('f(1, (x for x in a))')
197+
eq('f((x for x in a), 2)')
198+
eq('(((a)))', 'a')
199+
eq('(((a, b)))', '(a, b)')
200+
eq("(x := 10)")
201+
eq("f'{(x := 10):=10}'")
202+
eq("1 + 2 + 3")
203+
204+
def test_fstring_debug_annotations(self):
205+
# f-strings with '=' don't round trip very well, so set the expected
206+
# result explicitely.
207+
self.assertAnnotationEqual("f'{x=!r}'", expected="f'x={x!r}'")
208+
self.assertAnnotationEqual("f'{x=:}'", expected="f'x={x:}'")
209+
self.assertAnnotationEqual("f'{x=:.2f}'", expected="f'x={x:.2f}'")
210+
self.assertAnnotationEqual("f'{x=!r}'", expected="f'x={x!r}'")
211+
self.assertAnnotationEqual("f'{x=!a}'", expected="f'x={x!a}'")
212+
self.assertAnnotationEqual("f'{x=!s:*^20}'", expected="f'x={x!s:*^20}'")
213+
214+
def test_infinity_numbers(self):
215+
inf = "1e" + repr(sys.float_info.max_10_exp + 1)
216+
infj = f"{inf}j"
217+
self.assertAnnotationEqual("1e1000", expected=inf)
218+
self.assertAnnotationEqual("1e1000j", expected=infj)
219+
self.assertAnnotationEqual("-1e1000", expected=f"-{inf}")
220+
self.assertAnnotationEqual("3+1e1000j", expected=f"3 + {infj}")
221+
self.assertAnnotationEqual("(1e1000, 1e1000j)", expected=f"({inf}, {infj})")
222+
self.assertAnnotationEqual("'inf'")
223+
self.assertAnnotationEqual("('inf', 1e1000, 'infxxx', 1e1000j)", expected=f"('inf', {inf}, 'infxxx', {infj})")
224+
self.assertAnnotationEqual("(1e1000, (1e1000j,))", expected=f"({inf}, ({infj},))")
225+
226+
227+
if __name__ == "__main__":
228+
unittest.main()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Enable postponed evaluation of annotations (:pep:`563`) by default.

0 commit comments

Comments
 (0)