1import ast 2import types 3import decimal 4import unittest 5 6a_global = 'global variable' 7 8# You could argue that I'm too strict in looking for specific error 9# values with assertRaisesRegex, but without it it's way too easy to 10# make a syntax error in the test strings. Especially with all of the 11# triple quotes, raw strings, backslashes, etc. I think it's a 12# worthwhile tradeoff. When I switched to this method, I found many 13# examples where I wasn't testing what I thought I was. 14 15class TestCase(unittest.TestCase): 16 def assertAllRaise(self, exception_type, regex, error_strings): 17 for str in error_strings: 18 with self.subTest(str=str): 19 with self.assertRaisesRegex(exception_type, regex): 20 eval(str) 21 22 def test__format__lookup(self): 23 # Make sure __format__ is looked up on the type, not the instance. 24 class X: 25 def __format__(self, spec): 26 return 'class' 27 28 x = X() 29 30 # Add a bound __format__ method to the 'y' instance, but not 31 # the 'x' instance. 32 y = X() 33 y.__format__ = types.MethodType(lambda self, spec: 'instance', y) 34 35 self.assertEqual(f'{y}', format(y)) 36 self.assertEqual(f'{y}', 'class') 37 self.assertEqual(format(x), format(y)) 38 39 # __format__ is not called this way, but still make sure it 40 # returns what we expect (so we can make sure we're bypassing 41 # it). 42 self.assertEqual(x.__format__(''), 'class') 43 self.assertEqual(y.__format__(''), 'instance') 44 45 # This is how __format__ is actually called. 46 self.assertEqual(type(x).__format__(x, ''), 'class') 47 self.assertEqual(type(y).__format__(y, ''), 'class') 48 49 def test_ast(self): 50 # Inspired by http://bugs.python.org/issue24975 51 class X: 52 def __init__(self): 53 self.called = False 54 def __call__(self): 55 self.called = True 56 return 4 57 x = X() 58 expr = """ 59a = 10 60f'{a * x()}'""" 61 t = ast.parse(expr) 62 c = compile(t, '', 'exec') 63 64 # Make sure x was not called. 65 self.assertFalse(x.called) 66 67 # Actually run the code. 68 exec(c) 69 70 # Make sure x was called. 71 self.assertTrue(x.called) 72 73 def test_docstring(self): 74 def f(): 75 f'''Not a docstring''' 76 self.assertIsNone(f.__doc__) 77 def g(): 78 '''Not a docstring''' \ 79 f'' 80 self.assertIsNone(g.__doc__) 81 82 def test_literal_eval(self): 83 with self.assertRaisesRegex(ValueError, 'malformed node or string'): 84 ast.literal_eval("f'x'") 85 86 def test_ast_compile_time_concat(self): 87 x = [''] 88 89 expr = """x[0] = 'foo' f'{3}'""" 90 t = ast.parse(expr) 91 c = compile(t, '', 'exec') 92 exec(c) 93 self.assertEqual(x[0], 'foo3') 94 95 def test_compile_time_concat_errors(self): 96 self.assertAllRaise(SyntaxError, 97 'cannot mix bytes and nonbytes literals', 98 [r"""f'' b''""", 99 r"""b'' f''""", 100 ]) 101 102 def test_literal(self): 103 self.assertEqual(f'', '') 104 self.assertEqual(f'a', 'a') 105 self.assertEqual(f' ', ' ') 106 107 def test_unterminated_string(self): 108 self.assertAllRaise(SyntaxError, 'f-string: unterminated string', 109 [r"""f'{"x'""", 110 r"""f'{"x}'""", 111 r"""f'{("x'""", 112 r"""f'{("x}'""", 113 ]) 114 115 def test_mismatched_parens(self): 116 self.assertAllRaise(SyntaxError, 'f-string: mismatched', 117 ["f'{((}'", 118 ]) 119 120 def test_double_braces(self): 121 self.assertEqual(f'{{', '{') 122 self.assertEqual(f'a{{', 'a{') 123 self.assertEqual(f'{{b', '{b') 124 self.assertEqual(f'a{{b', 'a{b') 125 self.assertEqual(f'}}', '}') 126 self.assertEqual(f'a}}', 'a}') 127 self.assertEqual(f'}}b', '}b') 128 self.assertEqual(f'a}}b', 'a}b') 129 self.assertEqual(f'{{}}', '{}') 130 self.assertEqual(f'a{{}}', 'a{}') 131 self.assertEqual(f'{{b}}', '{b}') 132 self.assertEqual(f'{{}}c', '{}c') 133 self.assertEqual(f'a{{b}}', 'a{b}') 134 self.assertEqual(f'a{{}}c', 'a{}c') 135 self.assertEqual(f'{{b}}c', '{b}c') 136 self.assertEqual(f'a{{b}}c', 'a{b}c') 137 138 self.assertEqual(f'{{{10}', '{10') 139 self.assertEqual(f'}}{10}', '}10') 140 self.assertEqual(f'}}{{{10}', '}{10') 141 self.assertEqual(f'}}a{{{10}', '}a{10') 142 143 self.assertEqual(f'{10}{{', '10{') 144 self.assertEqual(f'{10}}}', '10}') 145 self.assertEqual(f'{10}}}{{', '10}{') 146 self.assertEqual(f'{10}}}a{{' '}', '10}a{}') 147 148 # Inside of strings, don't interpret doubled brackets. 149 self.assertEqual(f'{"{{}}"}', '{{}}') 150 151 self.assertAllRaise(TypeError, 'unhashable type', 152 ["f'{ {{}} }'", # dict in a set 153 ]) 154 155 def test_compile_time_concat(self): 156 x = 'def' 157 self.assertEqual('abc' f'## {x}ghi', 'abc## defghi') 158 self.assertEqual('abc' f'{x}' 'ghi', 'abcdefghi') 159 self.assertEqual('abc' f'{x}' 'gh' f'i{x:4}', 'abcdefghidef ') 160 self.assertEqual('{x}' f'{x}', '{x}def') 161 self.assertEqual('{x' f'{x}', '{xdef') 162 self.assertEqual('{x}' f'{x}', '{x}def') 163 self.assertEqual('{{x}}' f'{x}', '{{x}}def') 164 self.assertEqual('{{x' f'{x}', '{{xdef') 165 self.assertEqual('x}}' f'{x}', 'x}}def') 166 self.assertEqual(f'{x}' 'x}}', 'defx}}') 167 self.assertEqual(f'{x}' '', 'def') 168 self.assertEqual('' f'{x}' '', 'def') 169 self.assertEqual('' f'{x}', 'def') 170 self.assertEqual(f'{x}' '2', 'def2') 171 self.assertEqual('1' f'{x}' '2', '1def2') 172 self.assertEqual('1' f'{x}', '1def') 173 self.assertEqual(f'{x}' f'-{x}', 'def-def') 174 self.assertEqual('' f'', '') 175 self.assertEqual('' f'' '', '') 176 self.assertEqual('' f'' '' f'', '') 177 self.assertEqual(f'', '') 178 self.assertEqual(f'' '', '') 179 self.assertEqual(f'' '' f'', '') 180 self.assertEqual(f'' '' f'' '', '') 181 182 self.assertAllRaise(SyntaxError, "f-string: expecting '}'", 183 ["f'{3' f'}'", # can't concat to get a valid f-string 184 ]) 185 186 def test_comments(self): 187 # These aren't comments, since they're in strings. 188 d = {'#': 'hash'} 189 self.assertEqual(f'{"#"}', '#') 190 self.assertEqual(f'{d["#"]}', 'hash') 191 192 self.assertAllRaise(SyntaxError, "f-string expression part cannot include '#'", 193 ["f'{1#}'", # error because the expression becomes "(1#)" 194 "f'{3(#)}'", 195 "f'{#}'", 196 "f'{)#}'", # When wrapped in parens, this becomes 197 # '()#)'. Make sure that doesn't compile. 198 ]) 199 200 def test_many_expressions(self): 201 # Create a string with many expressions in it. Note that 202 # because we have a space in here as a literal, we're actually 203 # going to use twice as many ast nodes: one for each literal 204 # plus one for each expression. 205 def build_fstr(n, extra=''): 206 return "f'" + ('{x} ' * n) + extra + "'" 207 208 x = 'X' 209 width = 1 210 211 # Test around 256. 212 for i in range(250, 260): 213 self.assertEqual(eval(build_fstr(i)), (x+' ')*i) 214 215 # Test concatenating 2 largs fstrings. 216 self.assertEqual(eval(build_fstr(255)*256), (x+' ')*(255*256)) 217 218 s = build_fstr(253, '{x:{width}} ') 219 self.assertEqual(eval(s), (x+' ')*254) 220 221 # Test lots of expressions and constants, concatenated. 222 s = "f'{1}' 'x' 'y'" * 1024 223 self.assertEqual(eval(s), '1xy' * 1024) 224 225 def test_format_specifier_expressions(self): 226 width = 10 227 precision = 4 228 value = decimal.Decimal('12.34567') 229 self.assertEqual(f'result: {value:{width}.{precision}}', 'result: 12.35') 230 self.assertEqual(f'result: {value:{width!r}.{precision}}', 'result: 12.35') 231 self.assertEqual(f'result: {value:{width:0}.{precision:1}}', 'result: 12.35') 232 self.assertEqual(f'result: {value:{1}{0:0}.{precision:1}}', 'result: 12.35') 233 self.assertEqual(f'result: {value:{ 1}{ 0:0}.{ precision:1}}', 'result: 12.35') 234 self.assertEqual(f'{10:#{1}0x}', ' 0xa') 235 self.assertEqual(f'{10:{"#"}1{0}{"x"}}', ' 0xa') 236 self.assertEqual(f'{-10:-{"#"}1{0}x}', ' -0xa') 237 self.assertEqual(f'{-10:{"-"}#{1}0{"x"}}', ' -0xa') 238 self.assertEqual(f'{10:#{3 != {4:5} and width}x}', ' 0xa') 239 240 self.assertAllRaise(SyntaxError, "f-string: expecting '}'", 241 ["""f'{"s"!r{":10"}}'""", 242 243 # This looks like a nested format spec. 244 ]) 245 246 self.assertAllRaise(SyntaxError, "invalid syntax", 247 [# Invalid syntax inside a nested spec. 248 "f'{4:{/5}}'", 249 ]) 250 251 self.assertAllRaise(SyntaxError, "f-string: expressions nested too deeply", 252 [# Can't nest format specifiers. 253 "f'result: {value:{width:{0}}.{precision:1}}'", 254 ]) 255 256 self.assertAllRaise(SyntaxError, 'f-string: invalid conversion character', 257 [# No expansion inside conversion or for 258 # the : or ! itself. 259 """f'{"s"!{"r"}}'""", 260 ]) 261 262 def test_side_effect_order(self): 263 class X: 264 def __init__(self): 265 self.i = 0 266 def __format__(self, spec): 267 self.i += 1 268 return str(self.i) 269 270 x = X() 271 self.assertEqual(f'{x} {x}', '1 2') 272 273 def test_missing_expression(self): 274 self.assertAllRaise(SyntaxError, 'f-string: empty expression not allowed', 275 ["f'{}'", 276 "f'{ }'" 277 "f' {} '", 278 "f'{!r}'", 279 "f'{ !r}'", 280 "f'{10:{ }}'", 281 "f' { } '", 282 283 # Catch the empty expression before the 284 # invalid conversion. 285 "f'{!x}'", 286 "f'{ !xr}'", 287 "f'{!x:}'", 288 "f'{!x:a}'", 289 "f'{ !xr:}'", 290 "f'{ !xr:a}'", 291 292 "f'{!}'", 293 "f'{:}'", 294 295 # We find the empty expression before the 296 # missing closing brace. 297 "f'{!'", 298 "f'{!s:'", 299 "f'{:'", 300 "f'{:x'", 301 ]) 302 303 def test_parens_in_expressions(self): 304 self.assertEqual(f'{3,}', '(3,)') 305 306 # Add these because when an expression is evaluated, parens 307 # are added around it. But we shouldn't go from an invalid 308 # expression to a valid one. The added parens are just 309 # supposed to allow whitespace (including newlines). 310 self.assertAllRaise(SyntaxError, 'invalid syntax', 311 ["f'{,}'", 312 "f'{,}'", # this is (,), which is an error 313 ]) 314 315 self.assertAllRaise(SyntaxError, "f-string: expecting '}'", 316 ["f'{3)+(4}'", 317 ]) 318 319 self.assertAllRaise(SyntaxError, 'EOL while scanning string literal', 320 ["f'{\n}'", 321 ]) 322 323 def test_backslashes_in_string_part(self): 324 self.assertEqual(f'\t', '\t') 325 self.assertEqual(r'\t', '\\t') 326 self.assertEqual(rf'\t', '\\t') 327 self.assertEqual(f'{2}\t', '2\t') 328 self.assertEqual(f'{2}\t{3}', '2\t3') 329 self.assertEqual(f'\t{3}', '\t3') 330 331 self.assertEqual(f'\u0394', '\u0394') 332 self.assertEqual(r'\u0394', '\\u0394') 333 self.assertEqual(rf'\u0394', '\\u0394') 334 self.assertEqual(f'{2}\u0394', '2\u0394') 335 self.assertEqual(f'{2}\u0394{3}', '2\u03943') 336 self.assertEqual(f'\u0394{3}', '\u03943') 337 338 self.assertEqual(f'\U00000394', '\u0394') 339 self.assertEqual(r'\U00000394', '\\U00000394') 340 self.assertEqual(rf'\U00000394', '\\U00000394') 341 self.assertEqual(f'{2}\U00000394', '2\u0394') 342 self.assertEqual(f'{2}\U00000394{3}', '2\u03943') 343 self.assertEqual(f'\U00000394{3}', '\u03943') 344 345 self.assertEqual(f'\N{GREEK CAPITAL LETTER DELTA}', '\u0394') 346 self.assertEqual(f'{2}\N{GREEK CAPITAL LETTER DELTA}', '2\u0394') 347 self.assertEqual(f'{2}\N{GREEK CAPITAL LETTER DELTA}{3}', '2\u03943') 348 self.assertEqual(f'\N{GREEK CAPITAL LETTER DELTA}{3}', '\u03943') 349 self.assertEqual(f'2\N{GREEK CAPITAL LETTER DELTA}', '2\u0394') 350 self.assertEqual(f'2\N{GREEK CAPITAL LETTER DELTA}3', '2\u03943') 351 self.assertEqual(f'\N{GREEK CAPITAL LETTER DELTA}3', '\u03943') 352 353 self.assertEqual(f'\x20', ' ') 354 self.assertEqual(r'\x20', '\\x20') 355 self.assertEqual(rf'\x20', '\\x20') 356 self.assertEqual(f'{2}\x20', '2 ') 357 self.assertEqual(f'{2}\x20{3}', '2 3') 358 self.assertEqual(f'\x20{3}', ' 3') 359 360 self.assertEqual(f'2\x20', '2 ') 361 self.assertEqual(f'2\x203', '2 3') 362 self.assertEqual(f'\x203', ' 3') 363 364 def test_misformed_unicode_character_name(self): 365 # These test are needed because unicode names are parsed 366 # differently inside f-strings. 367 self.assertAllRaise(SyntaxError, r"\(unicode error\) 'unicodeescape' codec can't decode bytes in position .*: malformed \\N character escape", 368 [r"f'\N'", 369 r"f'\N{'", 370 r"f'\N{GREEK CAPITAL LETTER DELTA'", 371 372 # Here are the non-f-string versions, 373 # which should give the same errors. 374 r"'\N'", 375 r"'\N{'", 376 r"'\N{GREEK CAPITAL LETTER DELTA'", 377 ]) 378 379 def test_no_backslashes_in_expression_part(self): 380 self.assertAllRaise(SyntaxError, 'f-string expression part cannot include a backslash', 381 [r"f'{\'a\'}'", 382 r"f'{\t3}'", 383 r"f'{\}'", 384 r"rf'{\'a\'}'", 385 r"rf'{\t3}'", 386 r"rf'{\}'", 387 r"""rf'{"\N{LEFT CURLY BRACKET}"}'""", 388 r"f'{\n}'", 389 ]) 390 391 def test_no_escapes_for_braces(self): 392 """ 393 Only literal curly braces begin an expression. 394 """ 395 # \x7b is '{'. 396 self.assertEqual(f'\x7b1+1}}', '{1+1}') 397 self.assertEqual(f'\x7b1+1', '{1+1') 398 self.assertEqual(f'\u007b1+1', '{1+1') 399 self.assertEqual(f'\N{LEFT CURLY BRACKET}1+1\N{RIGHT CURLY BRACKET}', '{1+1}') 400 401 def test_newlines_in_expressions(self): 402 self.assertEqual(f'{0}', '0') 403 self.assertEqual(rf'''{3+ 4044}''', '7') 405 406 def test_lambda(self): 407 x = 5 408 self.assertEqual(f'{(lambda y:x*y)("8")!r}', "'88888'") 409 self.assertEqual(f'{(lambda y:x*y)("8")!r:10}', "'88888' ") 410 self.assertEqual(f'{(lambda y:x*y)("8"):10}', "88888 ") 411 412 # lambda doesn't work without parens, because the colon 413 # makes the parser think it's a format_spec 414 self.assertAllRaise(SyntaxError, 'unexpected EOF while parsing', 415 ["f'{lambda x:x}'", 416 ]) 417 418 def test_yield(self): 419 # Not terribly useful, but make sure the yield turns 420 # a function into a generator 421 def fn(y): 422 f'y:{yield y*2}' 423 424 g = fn(4) 425 self.assertEqual(next(g), 8) 426 427 def test_yield_send(self): 428 def fn(x): 429 yield f'x:{yield (lambda i: x * i)}' 430 431 g = fn(10) 432 the_lambda = next(g) 433 self.assertEqual(the_lambda(4), 40) 434 self.assertEqual(g.send('string'), 'x:string') 435 436 def test_expressions_with_triple_quoted_strings(self): 437 self.assertEqual(f"{'''x'''}", 'x') 438 self.assertEqual(f"{'''eric's'''}", "eric's") 439 440 # Test concatenation within an expression 441 self.assertEqual(f'{"x" """eric"s""" "y"}', 'xeric"sy') 442 self.assertEqual(f'{"x" """eric"s"""}', 'xeric"s') 443 self.assertEqual(f'{"""eric"s""" "y"}', 'eric"sy') 444 self.assertEqual(f'{"""x""" """eric"s""" "y"}', 'xeric"sy') 445 self.assertEqual(f'{"""x""" """eric"s""" """y"""}', 'xeric"sy') 446 self.assertEqual(f'{r"""x""" """eric"s""" """y"""}', 'xeric"sy') 447 448 def test_multiple_vars(self): 449 x = 98 450 y = 'abc' 451 self.assertEqual(f'{x}{y}', '98abc') 452 453 self.assertEqual(f'X{x}{y}', 'X98abc') 454 self.assertEqual(f'{x}X{y}', '98Xabc') 455 self.assertEqual(f'{x}{y}X', '98abcX') 456 457 self.assertEqual(f'X{x}Y{y}', 'X98Yabc') 458 self.assertEqual(f'X{x}{y}Y', 'X98abcY') 459 self.assertEqual(f'{x}X{y}Y', '98XabcY') 460 461 self.assertEqual(f'X{x}Y{y}Z', 'X98YabcZ') 462 463 def test_closure(self): 464 def outer(x): 465 def inner(): 466 return f'x:{x}' 467 return inner 468 469 self.assertEqual(outer('987')(), 'x:987') 470 self.assertEqual(outer(7)(), 'x:7') 471 472 def test_arguments(self): 473 y = 2 474 def f(x, width): 475 return f'x={x*y:{width}}' 476 477 self.assertEqual(f('foo', 10), 'x=foofoo ') 478 x = 'bar' 479 self.assertEqual(f(10, 10), 'x= 20') 480 481 def test_locals(self): 482 value = 123 483 self.assertEqual(f'v:{value}', 'v:123') 484 485 def test_missing_variable(self): 486 with self.assertRaises(NameError): 487 f'v:{value}' 488 489 def test_missing_format_spec(self): 490 class O: 491 def __format__(self, spec): 492 if not spec: 493 return '*' 494 return spec 495 496 self.assertEqual(f'{O():x}', 'x') 497 self.assertEqual(f'{O()}', '*') 498 self.assertEqual(f'{O():}', '*') 499 500 self.assertEqual(f'{3:}', '3') 501 self.assertEqual(f'{3!s:}', '3') 502 503 def test_global(self): 504 self.assertEqual(f'g:{a_global}', 'g:global variable') 505 self.assertEqual(f'g:{a_global!r}', "g:'global variable'") 506 507 a_local = 'local variable' 508 self.assertEqual(f'g:{a_global} l:{a_local}', 509 'g:global variable l:local variable') 510 self.assertEqual(f'g:{a_global!r}', 511 "g:'global variable'") 512 self.assertEqual(f'g:{a_global} l:{a_local!r}', 513 "g:global variable l:'local variable'") 514 515 self.assertIn("module 'unittest' from", f'{unittest}') 516 517 def test_shadowed_global(self): 518 a_global = 'really a local' 519 self.assertEqual(f'g:{a_global}', 'g:really a local') 520 self.assertEqual(f'g:{a_global!r}', "g:'really a local'") 521 522 a_local = 'local variable' 523 self.assertEqual(f'g:{a_global} l:{a_local}', 524 'g:really a local l:local variable') 525 self.assertEqual(f'g:{a_global!r}', 526 "g:'really a local'") 527 self.assertEqual(f'g:{a_global} l:{a_local!r}', 528 "g:really a local l:'local variable'") 529 530 def test_call(self): 531 def foo(x): 532 return 'x=' + str(x) 533 534 self.assertEqual(f'{foo(10)}', 'x=10') 535 536 def test_nested_fstrings(self): 537 y = 5 538 self.assertEqual(f'{f"{0}"*3}', '000') 539 self.assertEqual(f'{f"{y}"*3}', '555') 540 541 def test_invalid_string_prefixes(self): 542 self.assertAllRaise(SyntaxError, 'unexpected EOF while parsing', 543 ["fu''", 544 "uf''", 545 "Fu''", 546 "fU''", 547 "Uf''", 548 "uF''", 549 "ufr''", 550 "urf''", 551 "fur''", 552 "fru''", 553 "rfu''", 554 "ruf''", 555 "FUR''", 556 "Fur''", 557 "fb''", 558 "fB''", 559 "Fb''", 560 "FB''", 561 "bf''", 562 "bF''", 563 "Bf''", 564 "BF''", 565 ]) 566 567 def test_leading_trailing_spaces(self): 568 self.assertEqual(f'{ 3}', '3') 569 self.assertEqual(f'{ 3}', '3') 570 self.assertEqual(f'{3 }', '3') 571 self.assertEqual(f'{3 }', '3') 572 573 self.assertEqual(f'expr={ {x: y for x, y in [(1, 2), ]}}', 574 'expr={1: 2}') 575 self.assertEqual(f'expr={ {x: y for x, y in [(1, 2), ]} }', 576 'expr={1: 2}') 577 578 def test_not_equal(self): 579 # There's a special test for this because there's a special 580 # case in the f-string parser to look for != as not ending an 581 # expression. Normally it would, while looking for !s or !r. 582 583 self.assertEqual(f'{3!=4}', 'True') 584 self.assertEqual(f'{3!=4:}', 'True') 585 self.assertEqual(f'{3!=4!s}', 'True') 586 self.assertEqual(f'{3!=4!s:.3}', 'Tru') 587 588 def test_conversions(self): 589 self.assertEqual(f'{3.14:10.10}', ' 3.14') 590 self.assertEqual(f'{3.14!s:10.10}', '3.14 ') 591 self.assertEqual(f'{3.14!r:10.10}', '3.14 ') 592 self.assertEqual(f'{3.14!a:10.10}', '3.14 ') 593 594 self.assertEqual(f'{"a"}', 'a') 595 self.assertEqual(f'{"a"!r}', "'a'") 596 self.assertEqual(f'{"a"!a}', "'a'") 597 598 # Not a conversion. 599 self.assertEqual(f'{"a!r"}', "a!r") 600 601 # Not a conversion, but show that ! is allowed in a format spec. 602 self.assertEqual(f'{3.14:!<10.10}', '3.14!!!!!!') 603 604 self.assertAllRaise(SyntaxError, 'f-string: invalid conversion character', 605 ["f'{3!g}'", 606 "f'{3!A}'", 607 "f'{3!3}'", 608 "f'{3!G}'", 609 "f'{3!!}'", 610 "f'{3!:}'", 611 "f'{3! s}'", # no space before conversion char 612 ]) 613 614 self.assertAllRaise(SyntaxError, "f-string: expecting '}'", 615 ["f'{x!s{y}}'", 616 "f'{3!ss}'", 617 "f'{3!ss:}'", 618 "f'{3!ss:s}'", 619 ]) 620 621 def test_assignment(self): 622 self.assertAllRaise(SyntaxError, 'invalid syntax', 623 ["f'' = 3", 624 "f'{0}' = x", 625 "f'{x}' = x", 626 ]) 627 628 def test_del(self): 629 self.assertAllRaise(SyntaxError, 'invalid syntax', 630 ["del f''", 631 "del '' f''", 632 ]) 633 634 def test_mismatched_braces(self): 635 self.assertAllRaise(SyntaxError, "f-string: single '}' is not allowed", 636 ["f'{{}'", 637 "f'{{}}}'", 638 "f'}'", 639 "f'x}'", 640 "f'x}x'", 641 r"f'\u007b}'", 642 643 # Can't have { or } in a format spec. 644 "f'{3:}>10}'", 645 "f'{3:}}>10}'", 646 ]) 647 648 self.assertAllRaise(SyntaxError, "f-string: expecting '}'", 649 ["f'{3:{{>10}'", 650 "f'{3'", 651 "f'{3!'", 652 "f'{3:'", 653 "f'{3!s'", 654 "f'{3!s:'", 655 "f'{3!s:3'", 656 "f'x{'", 657 "f'x{x'", 658 "f'{x'", 659 "f'{3:s'", 660 "f'{{{'", 661 "f'{{}}{'", 662 "f'{'", 663 ]) 664 665 # But these are just normal strings. 666 self.assertEqual(f'{"{"}', '{') 667 self.assertEqual(f'{"}"}', '}') 668 self.assertEqual(f'{3:{"}"}>10}', '}}}}}}}}}3') 669 self.assertEqual(f'{2:{"{"}>10}', '{{{{{{{{{2') 670 671 def test_if_conditional(self): 672 # There's special logic in compile.c to test if the 673 # conditional for an if (and while) are constants. Exercise 674 # that code. 675 676 def test_fstring(x, expected): 677 flag = 0 678 if f'{x}': 679 flag = 1 680 else: 681 flag = 2 682 self.assertEqual(flag, expected) 683 684 def test_concat_empty(x, expected): 685 flag = 0 686 if '' f'{x}': 687 flag = 1 688 else: 689 flag = 2 690 self.assertEqual(flag, expected) 691 692 def test_concat_non_empty(x, expected): 693 flag = 0 694 if ' ' f'{x}': 695 flag = 1 696 else: 697 flag = 2 698 self.assertEqual(flag, expected) 699 700 test_fstring('', 2) 701 test_fstring(' ', 1) 702 703 test_concat_empty('', 2) 704 test_concat_empty(' ', 1) 705 706 test_concat_non_empty('', 1) 707 test_concat_non_empty(' ', 1) 708 709 def test_empty_format_specifier(self): 710 x = 'test' 711 self.assertEqual(f'{x}', 'test') 712 self.assertEqual(f'{x:}', 'test') 713 self.assertEqual(f'{x!s:}', 'test') 714 self.assertEqual(f'{x!r:}', "'test'") 715 716 def test_str_format_differences(self): 717 d = {'a': 'string', 718 0: 'integer', 719 } 720 a = 0 721 self.assertEqual(f'{d[0]}', 'integer') 722 self.assertEqual(f'{d["a"]}', 'string') 723 self.assertEqual(f'{d[a]}', 'integer') 724 self.assertEqual('{d[a]}'.format(d=d), 'string') 725 self.assertEqual('{d[0]}'.format(d=d), 'integer') 726 727 def test_invalid_expressions(self): 728 self.assertAllRaise(SyntaxError, 'invalid syntax', 729 [r"f'{a[4)}'", 730 r"f'{a(4]}'", 731 ]) 732 733 def test_errors(self): 734 # see issue 26287 735 self.assertAllRaise(TypeError, 'unsupported', 736 [r"f'{(lambda: 0):x}'", 737 r"f'{(0,):x}'", 738 ]) 739 self.assertAllRaise(ValueError, 'Unknown format code', 740 [r"f'{1000:j}'", 741 r"f'{1000:j}'", 742 ]) 743 744 def test_loop(self): 745 for i in range(1000): 746 self.assertEqual(f'i:{i}', 'i:' + str(i)) 747 748 def test_dict(self): 749 d = {'"': 'dquote', 750 "'": 'squote', 751 'foo': 'bar', 752 } 753 self.assertEqual(f'''{d["'"]}''', 'squote') 754 self.assertEqual(f"""{d['"']}""", 'dquote') 755 756 self.assertEqual(f'{d["foo"]}', 'bar') 757 self.assertEqual(f"{d['foo']}", 'bar') 758 759if __name__ == '__main__': 760 unittest.main() 761