Take the following code:
k = 'A'
d = {}
d[k := k.lower()] = 'b'
print(k)
print(d)
This gives the output one would expect:
a
{'a': 'b'}
However, for similar code in a project of mine, flake8 complained about such a walrus expression being a syntax error (on a github runner for python 3.8 at least; on my computer there seemingly wasn't an issue).
So my question is whether a walrus expression always has to be within parentheses, or if cases like these are correct too.
It doesn't need to be used in square brackets nor parentheses.
For instance in an if
block:
k = 'A'
if l:=k.lower() == 'a':
print(l)
Or:
s = ' a '
if x:=s.strip():
print(x)
PEP 572 gives some specific cases in which parentheses are required:
There are a few places where assignment expressions are not allowed, in order to avoid ambiguities or user confusion:
- Unparenthesized assignment expressions are prohibited at the top level of an expression statement. Example:
y := f(x) # INVALID (y := f(x)) # Valid, though not recommended
This rule is included to simplify the choice for the user between an assignment statement and an assignment expression – there is no syntactic position where both are valid.
- Unparenthesized assignment expressions are prohibited at the top level of the right hand side of an assignment statement. Example:
y0 = y1 := f(x) # INVALID y0 = (y1 := f(x)) # Valid, though discouraged
Again, this rule is included to avoid two visually similar ways of saying the same thing.
- Unparenthesized assignment expressions are prohibited for the value of a keyword argument in a call. Example:
foo(x = y := f(x)) # INVALID foo(x=(y := f(x))) # Valid, though probably confusing
This rule is included to disallow excessively confusing code, and because parsing keyword arguments is complex enough already.
- Unparenthesized assignment expressions are prohibited at the top level of a function default value. Example:
def foo(answer = p := 42): # INVALID ... def foo(answer=(p := 42)): # Valid, though not great style ...
This rule is included to discourage side effects in a position whose exact semantics are already confusing to many users (cf. the common style recommendation against mutable default values), and also to echo the similar prohibition in calls (the previous bullet).
- Unparenthesized assignment expressions are prohibited as annotations for arguments, return values and assignments. Example:
def foo(answer: p := 42 = 5): # INVALID ... def foo(answer: (p := 42) = 5): # Valid, but probably never useful ...
The reasoning here is similar to the two previous cases; this ungrouped assortment of symbols and operators composed of : and = is hard to read correctly.
- Unparenthesized assignment expressions are prohibited in lambda functions. Example:
(lambda: x := 1) # INVALID lambda: (x := 1) # Valid, but unlikely to be useful (x := lambda: 1) # Valid lambda line: (m := re.match(pattern, line)) and m.group(1) # Valid
This allows lambda to always bind less tightly than :=; having a name binding at the top level inside a lambda function is unlikely to be of value, as there is no way to make use of it. In cases where the name will be used more than once, the expression is likely to need parenthesizing anyway, so this prohibition will rarely affect code.
- 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 backwards compatibility, assignment operator usage inside of f-strings must be parenthesized. As noted above, this usage of the assignment operator is not recommended.