The issue you are having is that five
is only assigned if three
is True in this statement because of short circuiting:
if (three:= i%3==0) and (five:= i%5 ==0)
so five
commonly is not assigned causing either a NameError
or using a non-current value.
You can force a True value by forming a non-empty tuple with the walrus assignment inside of it then using three
and five
as you expect after that tuple.
It is no prettier than assigning three
and five
prior to the if
but this works:
arr=[]
for i in range(1,26):
if (three:=i%3==0, five:=i%5==0) and three and five:
arr.append(f"{i} FizzBuzz")
elif three:
arr.append(f"{i} Fizz")
elif five:
arr.append(f"{i} Buzz")
else:
arr.append(f"{i}")
>>> arr
['1', '2', '3 Fizz', '4', '5 Buzz', '6 Fizz', '7', '8', '9 Fizz', '10 Buzz', '11', '12 Fizz', '13', '14', '15 FizzBuzz', '16', '17', '18 Fizz', '19', '20 Buzz', '21 Fizz', '22', '23', '24 Fizz', '25 Buzz']
Any non-empty tuple is True
in Python. Forming it causes (three:=i%3==0, five:=i%5==0)
to always be truthy and three and five to be assigned each time. Since that tuple is true, the rest of the expression has to be evaluated with the correct values of three and five.
Alternatively, use if all((three:=i%3==0, five:=i%5==0)):
since the tuple is formed prior to testing its contents -- even though all short circuits; that would only happen after the tuple is formed.
Either of these forms allows easy refactoring into comprehensions:
arr=[f"{i} FizzBuzz" if three and five
else f"{i} Fizz" if three
else f"{i} Buzz" if five
else f"{i}"
for i in range(1,26) if (three:=i%3==0, five:=i%5==0)]
Or,
arr=[f"{i} FizzBuzz" if all((three:=i%3==0, five:=i%5==0))
else f"{i} Fizz" if three
else f"{i} Buzz" if five
else f"{i}" for i in range(1,26)]
Beware of the construction if (three := i % 3 == 0) & (five := i % 5 == 0):
if the result of each element is not boolean. You can get some unexpected failures:
>>> bool((x:=3) & (y:=4))
False
>>> bool((x:=3) and (y:=4))
True
The only way to fix that is have bool
applied to each:
>>> bool(x:=3) & bool(y:=4)
True
BTW, speaking of tuples, a shorter way to do a FizzBuzz type challenge in Python:
fb={(True,True):"{} FizzBuzz",
(True,False):"{} Fizz",
(False,True):"{} Buzz",
(False,False):"{}"}
arr=[fb[(i%3==0,i%5==0)].format(i) for i in range(1,26)]
And if you are looking for something new this type of problem is a natural for Python 3.10+ pattern matching:
arr=[]
for i in range(1,26):
s=f"{i}"
match (i%3==0,i%5==0):
case (True, (True | False) as oth):
s+=" FizzBuzz" if oth else " Fizz"
case (False, True):
s+=" Buzz"
arr.append(s)
<first condition> and <second condition>
, if<first condition>
is false, then` <second condition>` is never evaluated – Linaand
, not theif
statement. Are you wondering about the specific case of thisif
, or the generic problem of using:=
+and
? – Daimon