:=
lets you assign variables as part of larger expressions.
Example:
a = list(range(100))
# without the walrus:
# this is fine, but we call `len(a)` twice
if len(a) > 10:
print(f"List is too long ({len(a)} elements, expected <= 10)")
List is too long (100 elements, expected <= 10)
# with the walrus:
# we assign `n` within the `if` expression
# we only have to call `len(a)` once
if (n := len(a)) > 10:
print(f"List is too long ({n} elements, expected <= 10)")
List is too long (100 elements, expected <= 10)
Potential use cases:
- In list comps where a value used in a filtering expression is also needed in the expression body. Example:
features = [f"feature_{i:06}" for i in list(range(1, 20))]
[feat_no for feature in features if (feat_no := int(feature.removeprefix("feature_"))) % 3 == 0]
[3, 6, 9, 12, 15, 18]
from random import random
def f(x):
return x + random()
data = list(range(5))
[y for x in data if (y := f(x)) > 3]
[3.1250742782596097, 4.640482486271297]
- Resist calling a function more than once if it's expensive to compute
from time import sleep, time
def f():
sleep(5)
return 5
start = time()
[y := f(), y**2, y**3]
end = time()
print(f"{(end - start):.2f}s elasped")
5.00s elasped
- Update mutatable state in a list comp
total = 0
values = [1,4,10,3,4,5]
partial_sums = [total := total + v for v in values]
print("Total:", total)
Total: 27
- In
while
loops
print("\nwithout the walrus:")
n = 0
while(n < 10):
n +=1
print(n, end=",")
print("\n\nwith the walrus:")
n = 0
while(n := n +1) < 10:
print(n, end=",")
without the walrus:
1,2,3,4,5,6,7,8,9,10,
with the walrus:
1,2,3,4,5,6,7,8,9,
What not to do:
def I(x):
return x
y0 = (y1 := I(5)) # Valid, though discouraged
y0
5
def foo(x):
return f"I am {x}"
x = "X"
foo(x=(y := I(x))) # confusing
'I am X'
# here, a is keyword-only param:
def f(*, a):
return a + 1
f(1)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-11-83fd13e75879> in <module>
3 return a + 1
4
----> 5 f(1)
TypeError: f() takes 0 positional arguments but 1 was given
f(a=1)
2
# position-only param:
def f(a, /):
return a + 2
f(a=2)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-15-4a97820f29e2> in <module>
3 return a + 2
4
----> 5 f(a=2)
TypeError: f() got some positional-only arguments passed as keyword arguments: 'a'
f(2)
4
Why would you ever want to do this?
- To maintain consistency with existing C functions, like
sum
,len
,divmod
, and other builtins - If the names of the parameters are arbitrary, unhelpful, or subject to change
- For more flexibility with
kwargs
def f(a, b, /, **kwargs):
print(f"{a=}, {b=}")
print(f"{kwargs=}")
f(10, 20, a=1, b=2)
a=10, b=20
kwargs={'a': 1, 'b': 2}
name = "chuck"
emoj = "🍾"
number = 2
print(f"{name=} {emoj=} {abs(number)=}")
name='chuck' emoj='🍾' abs(number)=2
print(f"{name=!s} {emoj=!a} {abs(number)=:04}")
name=chuck emoj='\U0001f37e' abs(number)=0002
x = {"Washington": "DC", "McLean": "VA"}
y = {"McLean": "?", "New York": "NY"}
print(f"{x | y=}")
print(f"{y | x=}")
x | y={'Washington': 'DC', 'McLean': '?', 'New York': 'NY'}
y | x={'McLean': 'VA', 'New York': 'NY', 'Washington': 'DC'}
y |= {"Gaithersburg": "MD"}
print(f'{y=}')
y={'McLean': '?', 'New York': 'NY', 'Gaithersburg': 'MD'}
Can use list
instead of typing.List
, e.g.:
def greet_all(names: list[str]) -> None:
for name in names:
print("Hello", name)
tuple # typing.Tuple list # typing.List dict # typing.Dict set # typing.Set frozenset # typing.FrozenSet type # typing.Type collections.deque collections.defaultdict collections.OrderedDict collections.Counter collections.ChainMap collections.abc.Awaitable collections.abc.Coroutine collections.abc.AsyncIterable collections.abc.AsyncIterator collections.abc.AsyncGenerator collections.abc.Iterable collections.abc.Iterator collections.abc.Generator collections.abc.Reversible collections.abc.Container collections.abc.Collection collections.abc.Callable collections.abc.Set # typing.AbstractSet collections.abc.MutableSet collections.abc.Mapping collections.abc.MutableMapping collections.abc.Sequence collections.abc.MutableSequence collections.abc.ByteString collections.abc.MappingView collections.abc.KeysView collections.abc.ItemsView collections.abc.ValuesView contextlib.AbstractContextManager # typing.ContextManager contextlib.AbstractAsyncContextManager # typing.AsyncContextManager re.Pattern # typing.Pattern, typing.re.Pattern re.Match # typing.Match, typing.re.Match
import graphlib
graph = {"D": {"B", "C"}, "C": {"A"}, "B": {"A"}}
ts = graphlib.TopologicalSorter(graph)
tuple(ts.static_order())
('A', 'C', 'B', 'D')
[f.removeprefix("feature_") for f in features][:5]
['000001', '000002', '000003', '000004', '000005']