Search code examples
pythonf-stringpython-assignment-expression

Why do f-strings require parentheses around assignment expressions?


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?


Solution

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