1# -*- coding: utf-8 -*- 2# There are tests here with unicode string literals and 3# identifiers. There's a code in ast.c that was added because of a 4# failure with a non-ascii-only expression. So, I have tests for 5# that. There are workarounds that would let me run tests for that 6# code without unicode identifiers and strings, but just using them 7# directly seems like the easiest and therefore safest thing to do. 8# Unicode identifiers in tests is allowed by PEP 3131. 9 10import ast 11import types 12import decimal 13import unittest 14 15a_global = 'global variable' 16 17# You could argue that I'm too strict in looking for specific error 18# values with assertRaisesRegex, but without it it's way too easy to 19# make a syntax error in the test strings. Especially with all of the 20# triple quotes, raw strings, backslashes, etc. I think it's a 21# worthwhile tradeoff. When I switched to this method, I found many 22# examples where I wasn't testing what I thought I was. 23 24class TestCase(unittest.TestCase): 25 def assertAllRaise(self, exception_type, regex, error_strings): 26 for str in error_strings: 27 with self.subTest(str=str): 28 with self.assertRaisesRegex(exception_type, regex): 29 eval(str) 30 31 def test__format__lookup(self): 32 # Make sure __format__ is looked up on the type, not the instance. 33 class X: 34 def __format__(self, spec): 35 return 'class' 36 37 x = X() 38 39 # Add a bound __format__ method to the 'y' instance, but not 40 # the 'x' instance. 41 y = X() 42 y.__format__ = types.MethodType(lambda self, spec: 'instance', y) 43 44 self.assertEqual(f'{y}', format(y)) 45 self.assertEqual(f'{y}', 'class') 46 self.assertEqual(format(x), format(y)) 47 48 # __format__ is not called this way, but still make sure it 49 # returns what we expect (so we can make sure we're bypassing 50 # it). 51 self.assertEqual(x.__format__(''), 'class') 52 self.assertEqual(y.__format__(''), 'instance') 53 54 # This is how __format__ is actually called. 55 self.assertEqual(type(x).__format__(x, ''), 'class') 56 self.assertEqual(type(y).__format__(y, ''), 'class') 57 58 def test_ast(self): 59 # Inspired by http://bugs.python.org/issue24975 60 class X: 61 def __init__(self): 62 self.called = False 63 def __call__(self): 64 self.called = True 65 return 4 66 x = X() 67 expr = """ 68a = 10 69f'{a * x()}'""" 70 t = ast.parse(expr) 71 c = compile(t, '', 'exec') 72 73 # Make sure x was not called. 74 self.assertFalse(x.called) 75 76 # Actually run the code. 77 exec(c) 78 79 # Make sure x was called. 80 self.assertTrue(x.called) 81 82 def test_ast_line_numbers(self): 83 expr = """ 84a = 10 85f'{a * x()}'""" 86 t = ast.parse(expr) 87 self.assertEqual(type(t), ast.Module) 88 self.assertEqual(len(t.body), 2) 89 # check `a = 10` 90 self.assertEqual(type(t.body[0]), ast.Assign) 91 self.assertEqual(t.body[0].lineno, 2) 92 # check `f'...'` 93 self.assertEqual(type(t.body[1]), ast.Expr) 94 self.assertEqual(type(t.body[1].value), ast.JoinedStr) 95 self.assertEqual(len(t.body[1].value.values), 1) 96 self.assertEqual(type(t.body[1].value.values[0]), ast.FormattedValue) 97 self.assertEqual(t.body[1].lineno, 3) 98 self.assertEqual(t.body[1].value.lineno, 3) 99 self.assertEqual(t.body[1].value.values[0].lineno, 3) 100 # check the binop location 101 binop = t.body[1].value.values[0].value 102 self.assertEqual(type(binop), ast.BinOp) 103 self.assertEqual(type(binop.left), ast.Name) 104 self.assertEqual(type(binop.op), ast.Mult) 105 self.assertEqual(type(binop.right), ast.Call) 106 self.assertEqual(binop.lineno, 3) 107 self.assertEqual(binop.left.lineno, 3) 108 self.assertEqual(binop.right.lineno, 3) 109 self.assertEqual(binop.col_offset, 3) 110 self.assertEqual(binop.left.col_offset, 3) 111 self.assertEqual(binop.right.col_offset, 7) 112 113 def test_ast_line_numbers_multiple_formattedvalues(self): 114 expr = """ 115f'no formatted values' 116f'eggs {a * x()} spam {b + y()}'""" 117 t = ast.parse(expr) 118 self.assertEqual(type(t), ast.Module) 119 self.assertEqual(len(t.body), 2) 120 # check `f'no formatted value'` 121 self.assertEqual(type(t.body[0]), ast.Expr) 122 self.assertEqual(type(t.body[0].value), ast.JoinedStr) 123 self.assertEqual(t.body[0].lineno, 2) 124 # check `f'...'` 125 self.assertEqual(type(t.body[1]), ast.Expr) 126 self.assertEqual(type(t.body[1].value), ast.JoinedStr) 127 self.assertEqual(len(t.body[1].value.values), 4) 128 self.assertEqual(type(t.body[1].value.values[0]), ast.Constant) 129 self.assertEqual(type(t.body[1].value.values[0].value), str) 130 self.assertEqual(type(t.body[1].value.values[1]), ast.FormattedValue) 131 self.assertEqual(type(t.body[1].value.values[2]), ast.Constant) 132 self.assertEqual(type(t.body[1].value.values[2].value), str) 133 self.assertEqual(type(t.body[1].value.values[3]), ast.FormattedValue) 134 self.assertEqual(t.body[1].lineno, 3) 135 self.assertEqual(t.body[1].value.lineno, 3) 136 self.assertEqual(t.body[1].value.values[0].lineno, 3) 137 self.assertEqual(t.body[1].value.values[1].lineno, 3) 138 self.assertEqual(t.body[1].value.values[2].lineno, 3) 139 self.assertEqual(t.body[1].value.values[3].lineno, 3) 140 # check the first binop location 141 binop1 = t.body[1].value.values[1].value 142 self.assertEqual(type(binop1), ast.BinOp) 143 self.assertEqual(type(binop1.left), ast.Name) 144 self.assertEqual(type(binop1.op), ast.Mult) 145 self.assertEqual(type(binop1.right), ast.Call) 146 self.assertEqual(binop1.lineno, 3) 147 self.assertEqual(binop1.left.lineno, 3) 148 self.assertEqual(binop1.right.lineno, 3) 149 self.assertEqual(binop1.col_offset, 8) 150 self.assertEqual(binop1.left.col_offset, 8) 151 self.assertEqual(binop1.right.col_offset, 12) 152 # check the second binop location 153 binop2 = t.body[1].value.values[3].value 154 self.assertEqual(type(binop2), ast.BinOp) 155 self.assertEqual(type(binop2.left), ast.Name) 156 self.assertEqual(type(binop2.op), ast.Add) 157 self.assertEqual(type(binop2.right), ast.Call) 158 self.assertEqual(binop2.lineno, 3) 159 self.assertEqual(binop2.left.lineno, 3) 160 self.assertEqual(binop2.right.lineno, 3) 161 self.assertEqual(binop2.col_offset, 23) 162 self.assertEqual(binop2.left.col_offset, 23) 163 self.assertEqual(binop2.right.col_offset, 27) 164 165 def test_ast_line_numbers_nested(self): 166 expr = """ 167a = 10 168f'{a * f"-{x()}-"}'""" 169 t = ast.parse(expr) 170 self.assertEqual(type(t), ast.Module) 171 self.assertEqual(len(t.body), 2) 172 # check `a = 10` 173 self.assertEqual(type(t.body[0]), ast.Assign) 174 self.assertEqual(t.body[0].lineno, 2) 175 # check `f'...'` 176 self.assertEqual(type(t.body[1]), ast.Expr) 177 self.assertEqual(type(t.body[1].value), ast.JoinedStr) 178 self.assertEqual(len(t.body[1].value.values), 1) 179 self.assertEqual(type(t.body[1].value.values[0]), ast.FormattedValue) 180 self.assertEqual(t.body[1].lineno, 3) 181 self.assertEqual(t.body[1].value.lineno, 3) 182 self.assertEqual(t.body[1].value.values[0].lineno, 3) 183 # check the binop location 184 binop = t.body[1].value.values[0].value 185 self.assertEqual(type(binop), ast.BinOp) 186 self.assertEqual(type(binop.left), ast.Name) 187 self.assertEqual(type(binop.op), ast.Mult) 188 self.assertEqual(type(binop.right), ast.JoinedStr) 189 self.assertEqual(binop.lineno, 3) 190 self.assertEqual(binop.left.lineno, 3) 191 self.assertEqual(binop.right.lineno, 3) 192 self.assertEqual(binop.col_offset, 3) 193 self.assertEqual(binop.left.col_offset, 3) 194 self.assertEqual(binop.right.col_offset, 7) 195 # check the nested call location 196 self.assertEqual(len(binop.right.values), 3) 197 self.assertEqual(type(binop.right.values[0]), ast.Constant) 198 self.assertEqual(type(binop.right.values[0].value), str) 199 self.assertEqual(type(binop.right.values[1]), ast.FormattedValue) 200 self.assertEqual(type(binop.right.values[2]), ast.Constant) 201 self.assertEqual(type(binop.right.values[2].value), str) 202 self.assertEqual(binop.right.values[0].lineno, 3) 203 self.assertEqual(binop.right.values[1].lineno, 3) 204 self.assertEqual(binop.right.values[2].lineno, 3) 205 call = binop.right.values[1].value 206 self.assertEqual(type(call), ast.Call) 207 self.assertEqual(call.lineno, 3) 208 self.assertEqual(call.col_offset, 11) 209 210 def test_ast_line_numbers_duplicate_expression(self): 211 """Duplicate expression 212 213 NOTE: this is currently broken, always sets location of the first 214 expression. 215 """ 216 expr = """ 217a = 10 218f'{a * x()} {a * x()} {a * x()}' 219""" 220 t = ast.parse(expr) 221 self.assertEqual(type(t), ast.Module) 222 self.assertEqual(len(t.body), 2) 223 # check `a = 10` 224 self.assertEqual(type(t.body[0]), ast.Assign) 225 self.assertEqual(t.body[0].lineno, 2) 226 # check `f'...'` 227 self.assertEqual(type(t.body[1]), ast.Expr) 228 self.assertEqual(type(t.body[1].value), ast.JoinedStr) 229 self.assertEqual(len(t.body[1].value.values), 5) 230 self.assertEqual(type(t.body[1].value.values[0]), ast.FormattedValue) 231 self.assertEqual(type(t.body[1].value.values[1]), ast.Constant) 232 self.assertEqual(type(t.body[1].value.values[1].value), str) 233 self.assertEqual(type(t.body[1].value.values[2]), ast.FormattedValue) 234 self.assertEqual(type(t.body[1].value.values[3]), ast.Constant) 235 self.assertEqual(type(t.body[1].value.values[3].value), str) 236 self.assertEqual(type(t.body[1].value.values[4]), ast.FormattedValue) 237 self.assertEqual(t.body[1].lineno, 3) 238 self.assertEqual(t.body[1].value.lineno, 3) 239 self.assertEqual(t.body[1].value.values[0].lineno, 3) 240 self.assertEqual(t.body[1].value.values[1].lineno, 3) 241 self.assertEqual(t.body[1].value.values[2].lineno, 3) 242 self.assertEqual(t.body[1].value.values[3].lineno, 3) 243 self.assertEqual(t.body[1].value.values[4].lineno, 3) 244 # check the first binop location 245 binop = t.body[1].value.values[0].value 246 self.assertEqual(type(binop), ast.BinOp) 247 self.assertEqual(type(binop.left), ast.Name) 248 self.assertEqual(type(binop.op), ast.Mult) 249 self.assertEqual(type(binop.right), ast.Call) 250 self.assertEqual(binop.lineno, 3) 251 self.assertEqual(binop.left.lineno, 3) 252 self.assertEqual(binop.right.lineno, 3) 253 self.assertEqual(binop.col_offset, 3) 254 self.assertEqual(binop.left.col_offset, 3) 255 self.assertEqual(binop.right.col_offset, 7) 256 # check the second binop location 257 binop = t.body[1].value.values[2].value 258 self.assertEqual(type(binop), ast.BinOp) 259 self.assertEqual(type(binop.left), ast.Name) 260 self.assertEqual(type(binop.op), ast.Mult) 261 self.assertEqual(type(binop.right), ast.Call) 262 self.assertEqual(binop.lineno, 3) 263 self.assertEqual(binop.left.lineno, 3) 264 self.assertEqual(binop.right.lineno, 3) 265 self.assertEqual(binop.col_offset, 3) # FIXME: this is wrong 266 self.assertEqual(binop.left.col_offset, 3) # FIXME: this is wrong 267 self.assertEqual(binop.right.col_offset, 7) # FIXME: this is wrong 268 # check the third binop location 269 binop = t.body[1].value.values[4].value 270 self.assertEqual(type(binop), ast.BinOp) 271 self.assertEqual(type(binop.left), ast.Name) 272 self.assertEqual(type(binop.op), ast.Mult) 273 self.assertEqual(type(binop.right), ast.Call) 274 self.assertEqual(binop.lineno, 3) 275 self.assertEqual(binop.left.lineno, 3) 276 self.assertEqual(binop.right.lineno, 3) 277 self.assertEqual(binop.col_offset, 3) # FIXME: this is wrong 278 self.assertEqual(binop.left.col_offset, 3) # FIXME: this is wrong 279 self.assertEqual(binop.right.col_offset, 7) # FIXME: this is wrong 280 281 def test_ast_line_numbers_multiline_fstring(self): 282 # See bpo-30465 for details. 283 expr = """ 284a = 10 285f''' 286 {a 287 * 288 x()} 289non-important content 290''' 291""" 292 t = ast.parse(expr) 293 self.assertEqual(type(t), ast.Module) 294 self.assertEqual(len(t.body), 2) 295 # check `a = 10` 296 self.assertEqual(type(t.body[0]), ast.Assign) 297 self.assertEqual(t.body[0].lineno, 2) 298 # check `f'...'` 299 self.assertEqual(type(t.body[1]), ast.Expr) 300 self.assertEqual(type(t.body[1].value), ast.JoinedStr) 301 self.assertEqual(len(t.body[1].value.values), 3) 302 self.assertEqual(type(t.body[1].value.values[0]), ast.Constant) 303 self.assertEqual(type(t.body[1].value.values[0].value), str) 304 self.assertEqual(type(t.body[1].value.values[1]), ast.FormattedValue) 305 self.assertEqual(type(t.body[1].value.values[2]), ast.Constant) 306 self.assertEqual(type(t.body[1].value.values[2].value), str) 307 self.assertEqual(t.body[1].lineno, 3) 308 self.assertEqual(t.body[1].value.lineno, 3) 309 self.assertEqual(t.body[1].value.values[0].lineno, 3) 310 self.assertEqual(t.body[1].value.values[1].lineno, 3) 311 self.assertEqual(t.body[1].value.values[2].lineno, 3) 312 self.assertEqual(t.body[1].col_offset, 0) 313 self.assertEqual(t.body[1].value.col_offset, 0) 314 self.assertEqual(t.body[1].value.values[0].col_offset, 0) 315 self.assertEqual(t.body[1].value.values[1].col_offset, 0) 316 self.assertEqual(t.body[1].value.values[2].col_offset, 0) 317 # NOTE: the following lineno information and col_offset is correct for 318 # expressions within FormattedValues. 319 binop = t.body[1].value.values[1].value 320 self.assertEqual(type(binop), ast.BinOp) 321 self.assertEqual(type(binop.left), ast.Name) 322 self.assertEqual(type(binop.op), ast.Mult) 323 self.assertEqual(type(binop.right), ast.Call) 324 self.assertEqual(binop.lineno, 4) 325 self.assertEqual(binop.left.lineno, 4) 326 self.assertEqual(binop.right.lineno, 6) 327 self.assertEqual(binop.col_offset, 4) 328 self.assertEqual(binop.left.col_offset, 4) 329 self.assertEqual(binop.right.col_offset, 7) 330 331 def test_docstring(self): 332 def f(): 333 f'''Not a docstring''' 334 self.assertIsNone(f.__doc__) 335 def g(): 336 '''Not a docstring''' \ 337 f'' 338 self.assertIsNone(g.__doc__) 339 340 def test_literal_eval(self): 341 with self.assertRaisesRegex(ValueError, 'malformed node or string'): 342 ast.literal_eval("f'x'") 343 344 def test_ast_compile_time_concat(self): 345 x = [''] 346 347 expr = """x[0] = 'foo' f'{3}'""" 348 t = ast.parse(expr) 349 c = compile(t, '', 'exec') 350 exec(c) 351 self.assertEqual(x[0], 'foo3') 352 353 def test_compile_time_concat_errors(self): 354 self.assertAllRaise(SyntaxError, 355 'cannot mix bytes and nonbytes literals', 356 [r"""f'' b''""", 357 r"""b'' f''""", 358 ]) 359 360 def test_literal(self): 361 self.assertEqual(f'', '') 362 self.assertEqual(f'a', 'a') 363 self.assertEqual(f' ', ' ') 364 365 def test_unterminated_string(self): 366 self.assertAllRaise(SyntaxError, 'f-string: unterminated string', 367 [r"""f'{"x'""", 368 r"""f'{"x}'""", 369 r"""f'{("x'""", 370 r"""f'{("x}'""", 371 ]) 372 373 def test_mismatched_parens(self): 374 self.assertAllRaise(SyntaxError, r"f-string: closing parenthesis '\}' " 375 r"does not match opening parenthesis '\('", 376 ["f'{((}'", 377 ]) 378 self.assertAllRaise(SyntaxError, r"f-string: closing parenthesis '\)' " 379 r"does not match opening parenthesis '\['", 380 ["f'{a[4)}'", 381 ]) 382 self.assertAllRaise(SyntaxError, r"f-string: closing parenthesis '\]' " 383 r"does not match opening parenthesis '\('", 384 ["f'{a(4]}'", 385 ]) 386 self.assertAllRaise(SyntaxError, r"f-string: closing parenthesis '\}' " 387 r"does not match opening parenthesis '\['", 388 ["f'{a[4}'", 389 ]) 390 self.assertAllRaise(SyntaxError, r"f-string: closing parenthesis '\}' " 391 r"does not match opening parenthesis '\('", 392 ["f'{a(4}'", 393 ]) 394 self.assertRaises(SyntaxError, eval, "f'{" + "("*500 + "}'") 395 396 def test_double_braces(self): 397 self.assertEqual(f'{{', '{') 398 self.assertEqual(f'a{{', 'a{') 399 self.assertEqual(f'{{b', '{b') 400 self.assertEqual(f'a{{b', 'a{b') 401 self.assertEqual(f'}}', '}') 402 self.assertEqual(f'a}}', 'a}') 403 self.assertEqual(f'}}b', '}b') 404 self.assertEqual(f'a}}b', 'a}b') 405 self.assertEqual(f'{{}}', '{}') 406 self.assertEqual(f'a{{}}', 'a{}') 407 self.assertEqual(f'{{b}}', '{b}') 408 self.assertEqual(f'{{}}c', '{}c') 409 self.assertEqual(f'a{{b}}', 'a{b}') 410 self.assertEqual(f'a{{}}c', 'a{}c') 411 self.assertEqual(f'{{b}}c', '{b}c') 412 self.assertEqual(f'a{{b}}c', 'a{b}c') 413 414 self.assertEqual(f'{{{10}', '{10') 415 self.assertEqual(f'}}{10}', '}10') 416 self.assertEqual(f'}}{{{10}', '}{10') 417 self.assertEqual(f'}}a{{{10}', '}a{10') 418 419 self.assertEqual(f'{10}{{', '10{') 420 self.assertEqual(f'{10}}}', '10}') 421 self.assertEqual(f'{10}}}{{', '10}{') 422 self.assertEqual(f'{10}}}a{{' '}', '10}a{}') 423 424 # Inside of strings, don't interpret doubled brackets. 425 self.assertEqual(f'{"{{}}"}', '{{}}') 426 427 self.assertAllRaise(TypeError, 'unhashable type', 428 ["f'{ {{}} }'", # dict in a set 429 ]) 430 431 def test_compile_time_concat(self): 432 x = 'def' 433 self.assertEqual('abc' f'## {x}ghi', 'abc## defghi') 434 self.assertEqual('abc' f'{x}' 'ghi', 'abcdefghi') 435 self.assertEqual('abc' f'{x}' 'gh' f'i{x:4}', 'abcdefghidef ') 436 self.assertEqual('{x}' f'{x}', '{x}def') 437 self.assertEqual('{x' f'{x}', '{xdef') 438 self.assertEqual('{x}' f'{x}', '{x}def') 439 self.assertEqual('{{x}}' f'{x}', '{{x}}def') 440 self.assertEqual('{{x' f'{x}', '{{xdef') 441 self.assertEqual('x}}' f'{x}', 'x}}def') 442 self.assertEqual(f'{x}' 'x}}', 'defx}}') 443 self.assertEqual(f'{x}' '', 'def') 444 self.assertEqual('' f'{x}' '', 'def') 445 self.assertEqual('' f'{x}', 'def') 446 self.assertEqual(f'{x}' '2', 'def2') 447 self.assertEqual('1' f'{x}' '2', '1def2') 448 self.assertEqual('1' f'{x}', '1def') 449 self.assertEqual(f'{x}' f'-{x}', 'def-def') 450 self.assertEqual('' f'', '') 451 self.assertEqual('' f'' '', '') 452 self.assertEqual('' f'' '' f'', '') 453 self.assertEqual(f'', '') 454 self.assertEqual(f'' '', '') 455 self.assertEqual(f'' '' f'', '') 456 self.assertEqual(f'' '' f'' '', '') 457 458 self.assertAllRaise(SyntaxError, "f-string: expecting '}'", 459 ["f'{3' f'}'", # can't concat to get a valid f-string 460 ]) 461 462 def test_comments(self): 463 # These aren't comments, since they're in strings. 464 d = {'#': 'hash'} 465 self.assertEqual(f'{"#"}', '#') 466 self.assertEqual(f'{d["#"]}', 'hash') 467 468 self.assertAllRaise(SyntaxError, "f-string expression part cannot include '#'", 469 ["f'{1#}'", # error because the expression becomes "(1#)" 470 "f'{3(#)}'", 471 "f'{#}'", 472 ]) 473 self.assertAllRaise(SyntaxError, r"f-string: unmatched '\)'", 474 ["f'{)#}'", # When wrapped in parens, this becomes 475 # '()#)'. Make sure that doesn't compile. 476 ]) 477 478 def test_many_expressions(self): 479 # Create a string with many expressions in it. Note that 480 # because we have a space in here as a literal, we're actually 481 # going to use twice as many ast nodes: one for each literal 482 # plus one for each expression. 483 def build_fstr(n, extra=''): 484 return "f'" + ('{x} ' * n) + extra + "'" 485 486 x = 'X' 487 width = 1 488 489 # Test around 256. 490 for i in range(250, 260): 491 self.assertEqual(eval(build_fstr(i)), (x+' ')*i) 492 493 # Test concatenating 2 largs fstrings. 494 self.assertEqual(eval(build_fstr(255)*256), (x+' ')*(255*256)) 495 496 s = build_fstr(253, '{x:{width}} ') 497 self.assertEqual(eval(s), (x+' ')*254) 498 499 # Test lots of expressions and constants, concatenated. 500 s = "f'{1}' 'x' 'y'" * 1024 501 self.assertEqual(eval(s), '1xy' * 1024) 502 503 def test_format_specifier_expressions(self): 504 width = 10 505 precision = 4 506 value = decimal.Decimal('12.34567') 507 self.assertEqual(f'result: {value:{width}.{precision}}', 'result: 12.35') 508 self.assertEqual(f'result: {value:{width!r}.{precision}}', 'result: 12.35') 509 self.assertEqual(f'result: {value:{width:0}.{precision:1}}', 'result: 12.35') 510 self.assertEqual(f'result: {value:{1}{0:0}.{precision:1}}', 'result: 12.35') 511 self.assertEqual(f'result: {value:{ 1}{ 0:0}.{ precision:1}}', 'result: 12.35') 512 self.assertEqual(f'{10:#{1}0x}', ' 0xa') 513 self.assertEqual(f'{10:{"#"}1{0}{"x"}}', ' 0xa') 514 self.assertEqual(f'{-10:-{"#"}1{0}x}', ' -0xa') 515 self.assertEqual(f'{-10:{"-"}#{1}0{"x"}}', ' -0xa') 516 self.assertEqual(f'{10:#{3 != {4:5} and width}x}', ' 0xa') 517 518 self.assertAllRaise(SyntaxError, "f-string: expecting '}'", 519 ["""f'{"s"!r{":10"}}'""", 520 521 # This looks like a nested format spec. 522 ]) 523 524 self.assertAllRaise(SyntaxError, "invalid syntax", 525 [# Invalid syntax inside a nested spec. 526 "f'{4:{/5}}'", 527 ]) 528 529 self.assertAllRaise(SyntaxError, "f-string: expressions nested too deeply", 530 [# Can't nest format specifiers. 531 "f'result: {value:{width:{0}}.{precision:1}}'", 532 ]) 533 534 self.assertAllRaise(SyntaxError, 'f-string: invalid conversion character', 535 [# No expansion inside conversion or for 536 # the : or ! itself. 537 """f'{"s"!{"r"}}'""", 538 ]) 539 540 def test_side_effect_order(self): 541 class X: 542 def __init__(self): 543 self.i = 0 544 def __format__(self, spec): 545 self.i += 1 546 return str(self.i) 547 548 x = X() 549 self.assertEqual(f'{x} {x}', '1 2') 550 551 def test_missing_expression(self): 552 self.assertAllRaise(SyntaxError, 'f-string: empty expression not allowed', 553 ["f'{}'", 554 "f'{ }'" 555 "f' {} '", 556 "f'{!r}'", 557 "f'{ !r}'", 558 "f'{10:{ }}'", 559 "f' { } '", 560 561 # The Python parser ignores also the following 562 # whitespace characters in additional to a space. 563 "f'''{\t\f\r\n}'''", 564 565 # Catch the empty expression before the 566 # invalid conversion. 567 "f'{!x}'", 568 "f'{ !xr}'", 569 "f'{!x:}'", 570 "f'{!x:a}'", 571 "f'{ !xr:}'", 572 "f'{ !xr:a}'", 573 574 "f'{!}'", 575 "f'{:}'", 576 577 # We find the empty expression before the 578 # missing closing brace. 579 "f'{!'", 580 "f'{!s:'", 581 "f'{:'", 582 "f'{:x'", 583 ]) 584 585 # Different error message is raised for other whitespace characters. 586 self.assertAllRaise(SyntaxError, 'invalid character in identifier', 587 ["f'''{\xa0}'''", 588 "\xa0", 589 ]) 590 591 def test_parens_in_expressions(self): 592 self.assertEqual(f'{3,}', '(3,)') 593 594 # Add these because when an expression is evaluated, parens 595 # are added around it. But we shouldn't go from an invalid 596 # expression to a valid one. The added parens are just 597 # supposed to allow whitespace (including newlines). 598 self.assertAllRaise(SyntaxError, 'invalid syntax', 599 ["f'{,}'", 600 "f'{,}'", # this is (,), which is an error 601 ]) 602 603 self.assertAllRaise(SyntaxError, r"f-string: unmatched '\)'", 604 ["f'{3)+(4}'", 605 ]) 606 607 self.assertAllRaise(SyntaxError, 'EOL while scanning string literal', 608 ["f'{\n}'", 609 ]) 610 611 def test_backslashes_in_string_part(self): 612 self.assertEqual(f'\t', '\t') 613 self.assertEqual(r'\t', '\\t') 614 self.assertEqual(rf'\t', '\\t') 615 self.assertEqual(f'{2}\t', '2\t') 616 self.assertEqual(f'{2}\t{3}', '2\t3') 617 self.assertEqual(f'\t{3}', '\t3') 618 619 self.assertEqual(f'\u0394', '\u0394') 620 self.assertEqual(r'\u0394', '\\u0394') 621 self.assertEqual(rf'\u0394', '\\u0394') 622 self.assertEqual(f'{2}\u0394', '2\u0394') 623 self.assertEqual(f'{2}\u0394{3}', '2\u03943') 624 self.assertEqual(f'\u0394{3}', '\u03943') 625 626 self.assertEqual(f'\U00000394', '\u0394') 627 self.assertEqual(r'\U00000394', '\\U00000394') 628 self.assertEqual(rf'\U00000394', '\\U00000394') 629 self.assertEqual(f'{2}\U00000394', '2\u0394') 630 self.assertEqual(f'{2}\U00000394{3}', '2\u03943') 631 self.assertEqual(f'\U00000394{3}', '\u03943') 632 633 self.assertEqual(f'\N{GREEK CAPITAL LETTER DELTA}', '\u0394') 634 self.assertEqual(f'{2}\N{GREEK CAPITAL LETTER DELTA}', '2\u0394') 635 self.assertEqual(f'{2}\N{GREEK CAPITAL LETTER DELTA}{3}', '2\u03943') 636 self.assertEqual(f'\N{GREEK CAPITAL LETTER DELTA}{3}', '\u03943') 637 self.assertEqual(f'2\N{GREEK CAPITAL LETTER DELTA}', '2\u0394') 638 self.assertEqual(f'2\N{GREEK CAPITAL LETTER DELTA}3', '2\u03943') 639 self.assertEqual(f'\N{GREEK CAPITAL LETTER DELTA}3', '\u03943') 640 641 self.assertEqual(f'\x20', ' ') 642 self.assertEqual(r'\x20', '\\x20') 643 self.assertEqual(rf'\x20', '\\x20') 644 self.assertEqual(f'{2}\x20', '2 ') 645 self.assertEqual(f'{2}\x20{3}', '2 3') 646 self.assertEqual(f'\x20{3}', ' 3') 647 648 self.assertEqual(f'2\x20', '2 ') 649 self.assertEqual(f'2\x203', '2 3') 650 self.assertEqual(f'\x203', ' 3') 651 652 with self.assertWarns(DeprecationWarning): # invalid escape sequence 653 value = eval(r"f'\{6*7}'") 654 self.assertEqual(value, '\\42') 655 self.assertEqual(f'\\{6*7}', '\\42') 656 self.assertEqual(fr'\{6*7}', '\\42') 657 658 AMPERSAND = 'spam' 659 # Get the right unicode character (&), or pick up local variable 660 # depending on the number of backslashes. 661 self.assertEqual(f'\N{AMPERSAND}', '&') 662 self.assertEqual(f'\\N{AMPERSAND}', '\\Nspam') 663 self.assertEqual(fr'\N{AMPERSAND}', '\\Nspam') 664 self.assertEqual(f'\\\N{AMPERSAND}', '\\&') 665 666 def test_misformed_unicode_character_name(self): 667 # These test are needed because unicode names are parsed 668 # differently inside f-strings. 669 self.assertAllRaise(SyntaxError, r"\(unicode error\) 'unicodeescape' codec can't decode bytes in position .*: malformed \\N character escape", 670 [r"f'\N'", 671 r"f'\N{'", 672 r"f'\N{GREEK CAPITAL LETTER DELTA'", 673 674 # Here are the non-f-string versions, 675 # which should give the same errors. 676 r"'\N'", 677 r"'\N{'", 678 r"'\N{GREEK CAPITAL LETTER DELTA'", 679 ]) 680 681 def test_no_backslashes_in_expression_part(self): 682 self.assertAllRaise(SyntaxError, 'f-string expression part cannot include a backslash', 683 [r"f'{\'a\'}'", 684 r"f'{\t3}'", 685 r"f'{\}'", 686 r"rf'{\'a\'}'", 687 r"rf'{\t3}'", 688 r"rf'{\}'", 689 r"""rf'{"\N{LEFT CURLY BRACKET}"}'""", 690 r"f'{\n}'", 691 ]) 692 693 def test_no_escapes_for_braces(self): 694 """ 695 Only literal curly braces begin an expression. 696 """ 697 # \x7b is '{'. 698 self.assertEqual(f'\x7b1+1}}', '{1+1}') 699 self.assertEqual(f'\x7b1+1', '{1+1') 700 self.assertEqual(f'\u007b1+1', '{1+1') 701 self.assertEqual(f'\N{LEFT CURLY BRACKET}1+1\N{RIGHT CURLY BRACKET}', '{1+1}') 702 703 def test_newlines_in_expressions(self): 704 self.assertEqual(f'{0}', '0') 705 self.assertEqual(rf'''{3+ 7064}''', '7') 707 708 def test_lambda(self): 709 x = 5 710 self.assertEqual(f'{(lambda y:x*y)("8")!r}', "'88888'") 711 self.assertEqual(f'{(lambda y:x*y)("8")!r:10}', "'88888' ") 712 self.assertEqual(f'{(lambda y:x*y)("8"):10}', "88888 ") 713 714 # lambda doesn't work without parens, because the colon 715 # makes the parser think it's a format_spec 716 self.assertAllRaise(SyntaxError, 'unexpected EOF while parsing', 717 ["f'{lambda x:x}'", 718 ]) 719 720 def test_yield(self): 721 # Not terribly useful, but make sure the yield turns 722 # a function into a generator 723 def fn(y): 724 f'y:{yield y*2}' 725 726 g = fn(4) 727 self.assertEqual(next(g), 8) 728 729 def test_yield_send(self): 730 def fn(x): 731 yield f'x:{yield (lambda i: x * i)}' 732 733 g = fn(10) 734 the_lambda = next(g) 735 self.assertEqual(the_lambda(4), 40) 736 self.assertEqual(g.send('string'), 'x:string') 737 738 def test_expressions_with_triple_quoted_strings(self): 739 self.assertEqual(f"{'''x'''}", 'x') 740 self.assertEqual(f"{'''eric's'''}", "eric's") 741 742 # Test concatenation within an expression 743 self.assertEqual(f'{"x" """eric"s""" "y"}', 'xeric"sy') 744 self.assertEqual(f'{"x" """eric"s"""}', 'xeric"s') 745 self.assertEqual(f'{"""eric"s""" "y"}', 'eric"sy') 746 self.assertEqual(f'{"""x""" """eric"s""" "y"}', 'xeric"sy') 747 self.assertEqual(f'{"""x""" """eric"s""" """y"""}', 'xeric"sy') 748 self.assertEqual(f'{r"""x""" """eric"s""" """y"""}', 'xeric"sy') 749 750 def test_multiple_vars(self): 751 x = 98 752 y = 'abc' 753 self.assertEqual(f'{x}{y}', '98abc') 754 755 self.assertEqual(f'X{x}{y}', 'X98abc') 756 self.assertEqual(f'{x}X{y}', '98Xabc') 757 self.assertEqual(f'{x}{y}X', '98abcX') 758 759 self.assertEqual(f'X{x}Y{y}', 'X98Yabc') 760 self.assertEqual(f'X{x}{y}Y', 'X98abcY') 761 self.assertEqual(f'{x}X{y}Y', '98XabcY') 762 763 self.assertEqual(f'X{x}Y{y}Z', 'X98YabcZ') 764 765 def test_closure(self): 766 def outer(x): 767 def inner(): 768 return f'x:{x}' 769 return inner 770 771 self.assertEqual(outer('987')(), 'x:987') 772 self.assertEqual(outer(7)(), 'x:7') 773 774 def test_arguments(self): 775 y = 2 776 def f(x, width): 777 return f'x={x*y:{width}}' 778 779 self.assertEqual(f('foo', 10), 'x=foofoo ') 780 x = 'bar' 781 self.assertEqual(f(10, 10), 'x= 20') 782 783 def test_locals(self): 784 value = 123 785 self.assertEqual(f'v:{value}', 'v:123') 786 787 def test_missing_variable(self): 788 with self.assertRaises(NameError): 789 f'v:{value}' 790 791 def test_missing_format_spec(self): 792 class O: 793 def __format__(self, spec): 794 if not spec: 795 return '*' 796 return spec 797 798 self.assertEqual(f'{O():x}', 'x') 799 self.assertEqual(f'{O()}', '*') 800 self.assertEqual(f'{O():}', '*') 801 802 self.assertEqual(f'{3:}', '3') 803 self.assertEqual(f'{3!s:}', '3') 804 805 def test_global(self): 806 self.assertEqual(f'g:{a_global}', 'g:global variable') 807 self.assertEqual(f'g:{a_global!r}', "g:'global variable'") 808 809 a_local = 'local variable' 810 self.assertEqual(f'g:{a_global} l:{a_local}', 811 'g:global variable l:local variable') 812 self.assertEqual(f'g:{a_global!r}', 813 "g:'global variable'") 814 self.assertEqual(f'g:{a_global} l:{a_local!r}', 815 "g:global variable l:'local variable'") 816 817 self.assertIn("module 'unittest' from", f'{unittest}') 818 819 def test_shadowed_global(self): 820 a_global = 'really a local' 821 self.assertEqual(f'g:{a_global}', 'g:really a local') 822 self.assertEqual(f'g:{a_global!r}', "g:'really a local'") 823 824 a_local = 'local variable' 825 self.assertEqual(f'g:{a_global} l:{a_local}', 826 'g:really a local l:local variable') 827 self.assertEqual(f'g:{a_global!r}', 828 "g:'really a local'") 829 self.assertEqual(f'g:{a_global} l:{a_local!r}', 830 "g:really a local l:'local variable'") 831 832 def test_call(self): 833 def foo(x): 834 return 'x=' + str(x) 835 836 self.assertEqual(f'{foo(10)}', 'x=10') 837 838 def test_nested_fstrings(self): 839 y = 5 840 self.assertEqual(f'{f"{0}"*3}', '000') 841 self.assertEqual(f'{f"{y}"*3}', '555') 842 843 def test_invalid_string_prefixes(self): 844 self.assertAllRaise(SyntaxError, 'unexpected EOF while parsing', 845 ["fu''", 846 "uf''", 847 "Fu''", 848 "fU''", 849 "Uf''", 850 "uF''", 851 "ufr''", 852 "urf''", 853 "fur''", 854 "fru''", 855 "rfu''", 856 "ruf''", 857 "FUR''", 858 "Fur''", 859 "fb''", 860 "fB''", 861 "Fb''", 862 "FB''", 863 "bf''", 864 "bF''", 865 "Bf''", 866 "BF''", 867 ]) 868 869 def test_leading_trailing_spaces(self): 870 self.assertEqual(f'{ 3}', '3') 871 self.assertEqual(f'{ 3}', '3') 872 self.assertEqual(f'{3 }', '3') 873 self.assertEqual(f'{3 }', '3') 874 875 self.assertEqual(f'expr={ {x: y for x, y in [(1, 2), ]}}', 876 'expr={1: 2}') 877 self.assertEqual(f'expr={ {x: y for x, y in [(1, 2), ]} }', 878 'expr={1: 2}') 879 880 def test_not_equal(self): 881 # There's a special test for this because there's a special 882 # case in the f-string parser to look for != as not ending an 883 # expression. Normally it would, while looking for !s or !r. 884 885 self.assertEqual(f'{3!=4}', 'True') 886 self.assertEqual(f'{3!=4:}', 'True') 887 self.assertEqual(f'{3!=4!s}', 'True') 888 self.assertEqual(f'{3!=4!s:.3}', 'Tru') 889 890 def test_equal_equal(self): 891 # Because an expression ending in = has special meaning, 892 # there's a special test for ==. Make sure it works. 893 894 self.assertEqual(f'{0==1}', 'False') 895 896 def test_conversions(self): 897 self.assertEqual(f'{3.14:10.10}', ' 3.14') 898 self.assertEqual(f'{3.14!s:10.10}', '3.14 ') 899 self.assertEqual(f'{3.14!r:10.10}', '3.14 ') 900 self.assertEqual(f'{3.14!a:10.10}', '3.14 ') 901 902 self.assertEqual(f'{"a"}', 'a') 903 self.assertEqual(f'{"a"!r}', "'a'") 904 self.assertEqual(f'{"a"!a}', "'a'") 905 906 # Not a conversion. 907 self.assertEqual(f'{"a!r"}', "a!r") 908 909 # Not a conversion, but show that ! is allowed in a format spec. 910 self.assertEqual(f'{3.14:!<10.10}', '3.14!!!!!!') 911 912 self.assertAllRaise(SyntaxError, 'f-string: invalid conversion character', 913 ["f'{3!g}'", 914 "f'{3!A}'", 915 "f'{3!3}'", 916 "f'{3!G}'", 917 "f'{3!!}'", 918 "f'{3!:}'", 919 "f'{3! s}'", # no space before conversion char 920 ]) 921 922 self.assertAllRaise(SyntaxError, "f-string: expecting '}'", 923 ["f'{x!s{y}}'", 924 "f'{3!ss}'", 925 "f'{3!ss:}'", 926 "f'{3!ss:s}'", 927 ]) 928 929 def test_assignment(self): 930 self.assertAllRaise(SyntaxError, 'invalid syntax', 931 ["f'' = 3", 932 "f'{0}' = x", 933 "f'{x}' = x", 934 ]) 935 936 def test_del(self): 937 self.assertAllRaise(SyntaxError, 'invalid syntax', 938 ["del f''", 939 "del '' f''", 940 ]) 941 942 def test_mismatched_braces(self): 943 self.assertAllRaise(SyntaxError, "f-string: single '}' is not allowed", 944 ["f'{{}'", 945 "f'{{}}}'", 946 "f'}'", 947 "f'x}'", 948 "f'x}x'", 949 r"f'\u007b}'", 950 951 # Can't have { or } in a format spec. 952 "f'{3:}>10}'", 953 "f'{3:}}>10}'", 954 ]) 955 956 self.assertAllRaise(SyntaxError, "f-string: expecting '}'", 957 ["f'{3:{{>10}'", 958 "f'{3'", 959 "f'{3!'", 960 "f'{3:'", 961 "f'{3!s'", 962 "f'{3!s:'", 963 "f'{3!s:3'", 964 "f'x{'", 965 "f'x{x'", 966 "f'{x'", 967 "f'{3:s'", 968 "f'{{{'", 969 "f'{{}}{'", 970 "f'{'", 971 ]) 972 973 # But these are just normal strings. 974 self.assertEqual(f'{"{"}', '{') 975 self.assertEqual(f'{"}"}', '}') 976 self.assertEqual(f'{3:{"}"}>10}', '}}}}}}}}}3') 977 self.assertEqual(f'{2:{"{"}>10}', '{{{{{{{{{2') 978 979 def test_if_conditional(self): 980 # There's special logic in compile.c to test if the 981 # conditional for an if (and while) are constants. Exercise 982 # that code. 983 984 def test_fstring(x, expected): 985 flag = 0 986 if f'{x}': 987 flag = 1 988 else: 989 flag = 2 990 self.assertEqual(flag, expected) 991 992 def test_concat_empty(x, expected): 993 flag = 0 994 if '' f'{x}': 995 flag = 1 996 else: 997 flag = 2 998 self.assertEqual(flag, expected) 999 1000 def test_concat_non_empty(x, expected): 1001 flag = 0 1002 if ' ' f'{x}': 1003 flag = 1 1004 else: 1005 flag = 2 1006 self.assertEqual(flag, expected) 1007 1008 test_fstring('', 2) 1009 test_fstring(' ', 1) 1010 1011 test_concat_empty('', 2) 1012 test_concat_empty(' ', 1) 1013 1014 test_concat_non_empty('', 1) 1015 test_concat_non_empty(' ', 1) 1016 1017 def test_empty_format_specifier(self): 1018 x = 'test' 1019 self.assertEqual(f'{x}', 'test') 1020 self.assertEqual(f'{x:}', 'test') 1021 self.assertEqual(f'{x!s:}', 'test') 1022 self.assertEqual(f'{x!r:}', "'test'") 1023 1024 def test_str_format_differences(self): 1025 d = {'a': 'string', 1026 0: 'integer', 1027 } 1028 a = 0 1029 self.assertEqual(f'{d[0]}', 'integer') 1030 self.assertEqual(f'{d["a"]}', 'string') 1031 self.assertEqual(f'{d[a]}', 'integer') 1032 self.assertEqual('{d[a]}'.format(d=d), 'string') 1033 self.assertEqual('{d[0]}'.format(d=d), 'integer') 1034 1035 def test_errors(self): 1036 # see issue 26287 1037 self.assertAllRaise(TypeError, 'unsupported', 1038 [r"f'{(lambda: 0):x}'", 1039 r"f'{(0,):x}'", 1040 ]) 1041 self.assertAllRaise(ValueError, 'Unknown format code', 1042 [r"f'{1000:j}'", 1043 r"f'{1000:j}'", 1044 ]) 1045 1046 def test_loop(self): 1047 for i in range(1000): 1048 self.assertEqual(f'i:{i}', 'i:' + str(i)) 1049 1050 def test_dict(self): 1051 d = {'"': 'dquote', 1052 "'": 'squote', 1053 'foo': 'bar', 1054 } 1055 self.assertEqual(f'''{d["'"]}''', 'squote') 1056 self.assertEqual(f"""{d['"']}""", 'dquote') 1057 1058 self.assertEqual(f'{d["foo"]}', 'bar') 1059 self.assertEqual(f"{d['foo']}", 'bar') 1060 1061 def test_backslash_char(self): 1062 # Check eval of a backslash followed by a control char. 1063 # See bpo-30682: this used to raise an assert in pydebug mode. 1064 self.assertEqual(eval('f"\\\n"'), '') 1065 self.assertEqual(eval('f"\\\r"'), '') 1066 1067 def test_debug_conversion(self): 1068 x = 'A string' 1069 self.assertEqual(f'{x=}', 'x=' + repr(x)) 1070 self.assertEqual(f'{x =}', 'x =' + repr(x)) 1071 self.assertEqual(f'{x=!s}', 'x=' + str(x)) 1072 self.assertEqual(f'{x=!r}', 'x=' + repr(x)) 1073 self.assertEqual(f'{x=!a}', 'x=' + ascii(x)) 1074 1075 x = 2.71828 1076 self.assertEqual(f'{x=:.2f}', 'x=' + format(x, '.2f')) 1077 self.assertEqual(f'{x=:}', 'x=' + format(x, '')) 1078 self.assertEqual(f'{x=!r:^20}', 'x=' + format(repr(x), '^20')) 1079 self.assertEqual(f'{x=!s:^20}', 'x=' + format(str(x), '^20')) 1080 self.assertEqual(f'{x=!a:^20}', 'x=' + format(ascii(x), '^20')) 1081 1082 x = 9 1083 self.assertEqual(f'{3*x+15=}', '3*x+15=42') 1084 1085 # There is code in ast.c that deals with non-ascii expression values. So, 1086 # use a unicode identifier to trigger that. 1087 tenπ = 31.4 1088 self.assertEqual(f'{tenπ=:.2f}', 'tenπ=31.40') 1089 1090 # Also test with Unicode in non-identifiers. 1091 self.assertEqual(f'{"Σ"=}', '"Σ"=\'Σ\'') 1092 1093 # Make sure nested fstrings still work. 1094 self.assertEqual(f'{f"{3.1415=:.1f}":*^20}', '*****3.1415=3.1*****') 1095 1096 # Make sure text before and after an expression with = works 1097 # correctly. 1098 pi = 'π' 1099 self.assertEqual(f'alpha α {pi=} ω omega', "alpha α pi='π' ω omega") 1100 1101 # Check multi-line expressions. 1102 self.assertEqual(f'''{ 11033 1104=}''', '\n3\n=3') 1105 1106 # Since = is handled specially, make sure all existing uses of 1107 # it still work. 1108 1109 self.assertEqual(f'{0==1}', 'False') 1110 self.assertEqual(f'{0!=1}', 'True') 1111 self.assertEqual(f'{0<=1}', 'True') 1112 self.assertEqual(f'{0>=1}', 'False') 1113 self.assertEqual(f'{(x:="5")}', '5') 1114 self.assertEqual(x, '5') 1115 self.assertEqual(f'{(x:=5)}', '5') 1116 self.assertEqual(x, 5) 1117 self.assertEqual(f'{"="}', '=') 1118 1119 x = 20 1120 # This isn't an assignment expression, it's 'x', with a format 1121 # spec of '=10'. See test_walrus: you need to use parens. 1122 self.assertEqual(f'{x:=10}', ' 20') 1123 1124 # Test named function parameters, to make sure '=' parsing works 1125 # there. 1126 def f(a): 1127 nonlocal x 1128 oldx = x 1129 x = a 1130 return oldx 1131 x = 0 1132 self.assertEqual(f'{f(a="3=")}', '0') 1133 self.assertEqual(x, '3=') 1134 self.assertEqual(f'{f(a=4)}', '3=') 1135 self.assertEqual(x, 4) 1136 1137 # Make sure __format__ is being called. 1138 class C: 1139 def __format__(self, s): 1140 return f'FORMAT-{s}' 1141 def __repr__(self): 1142 return 'REPR' 1143 1144 self.assertEqual(f'{C()=}', 'C()=REPR') 1145 self.assertEqual(f'{C()=!r}', 'C()=REPR') 1146 self.assertEqual(f'{C()=:}', 'C()=FORMAT-') 1147 self.assertEqual(f'{C()=: }', 'C()=FORMAT- ') 1148 self.assertEqual(f'{C()=:x}', 'C()=FORMAT-x') 1149 self.assertEqual(f'{C()=!r:*^20}', 'C()=********REPR********') 1150 1151 self.assertRaises(SyntaxError, eval, "f'{C=]'") 1152 1153 # Make sure leading and following text works. 1154 x = 'foo' 1155 self.assertEqual(f'X{x=}Y', 'Xx='+repr(x)+'Y') 1156 1157 # Make sure whitespace around the = works. 1158 self.assertEqual(f'X{x =}Y', 'Xx ='+repr(x)+'Y') 1159 self.assertEqual(f'X{x= }Y', 'Xx= '+repr(x)+'Y') 1160 self.assertEqual(f'X{x = }Y', 'Xx = '+repr(x)+'Y') 1161 1162 # These next lines contains tabs. Backslash escapes don't 1163 # work in f-strings. 1164 # patchcheck doesn't like these tabs. So the only way to test 1165 # this will be to dynamically created and exec the f-strings. But 1166 # that's such a hassle I'll save it for another day. For now, convert 1167 # the tabs to spaces just to shut up patchcheck. 1168 #self.assertEqual(f'X{x =}Y', 'Xx\t='+repr(x)+'Y') 1169 #self.assertEqual(f'X{x = }Y', 'Xx\t=\t'+repr(x)+'Y') 1170 1171 def test_walrus(self): 1172 x = 20 1173 # This isn't an assignment expression, it's 'x', with a format 1174 # spec of '=10'. 1175 self.assertEqual(f'{x:=10}', ' 20') 1176 1177 # This is an assignment expression, which requires parens. 1178 self.assertEqual(f'{(x:=10)}', '10') 1179 self.assertEqual(x, 10) 1180 1181 1182if __name__ == '__main__': 1183 unittest.main() 1184