Why do f-strings require parentheses around assignment expressions?

1.6k Views Asked by At

In Python (3.11) why does the use of an assignment expression (the "walrus operator") require wrapping in parentheses when used inside an f-string?

For example:

#!/usr/bin/env python

from pathlib import Path

import torch


DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")

ckpt_dir = Path("/home/path/to/checkpoints")

_ckpt = next(ckpt_dir.iterdir())
print(_ckpt)
sdict = torch.load(_ckpt, map_location=DEVICE)

model_dict = sdict["state_dict"]

for k, v in model_dict.items():
    print(k)
    print(type(v))
    print(_shape := v.size())
    print(f"{(_numel := v.numel())}")
    print(_numel == torch.prod(torch.tensor(_shape)))

The code block above with print(f"{_numel := v.numel()}") instead does not parse.

What about the parsing / AST creation mandates this?

2

There are 2 best solutions below

0
Brian61354270 On BEST ANSWER

This behavior was explicitly specified in the original PEP for the assignment expressions (aka the walrus operator).

The reason for this was to preserve backward compatibility with formatted string literals. Before assignment expressions were added, you could already write f-strings like f"{x:=y}", which meant "format x using the format specification =y".

Quoting PEP 572 – Assignment Expressions:

Assignment expressions inside of f-strings require parentheses. Example:

>>> f'{(x:=10)}'  # Valid, uses assignment expression
'10'
>>> x = 10
>>> f'{x:=10}'    # Valid, passes '=10' to formatter
'        10'

This shows that what looks like an assignment operator in an f-string is not always an assignment operator. The f-string parser uses : to indicate formatting options. To preserve backward compatibility, assignment operator usage inside of f-strings must be parenthesized. As noted above, this usage of the assignment operator is not recommended.

1
Ahmed AEK On

Both the colon : and the = have meanings inside f-strings, and the walrus := does the same as just a colon :.

x = 4
print(f"{x=}")  # prints "x = 4"
print(f"{x:10}")  # prints output in 10 spaces
print(f"{x:=10}")  # does same as :

The disassembly of {x:=10} just loads =10 and passes it to the colon format operation.

  5           4 LOAD_GLOBAL              0 (print)
              6 LOAD_FAST                0 (x)
              8 LOAD_CONST               2 ('=10')
             10 FORMAT_VALUE             4 (with format)
             12 CALL_FUNCTION            1
             14 POP_TOP
             16 LOAD_CONST               0 (None)
             18 RETURN_VALUE