【问题标题】:Two Walrus Operators in one If Statement两个海象运算符在一个 If 语句中
【发布时间】:2022-01-13 15:03:39
【问题描述】:

有没有正确的方法在 1 个 if 语句中包含两个海象运算符?

if (three:= i%3==0) and (five:= i%5 ==0):
    arr.append("FizzBuzz")
elif three:
    arr.append("Fizz")
elif five:
    arr.append("Buzz")
else:
    arr.append(str(i-1))

此示例适用于 three,但 five 将“未定义”。

【问题讨论】:

  • 逻辑运算符短路。所以这里以<first condition> and <second condition>为例,如果<first condition>为假,那么``是从不评估
  • 这里的重要部分是and 的短路,而不是if 语句。你是想知道这个if的具体情况,还是使用:=+and的通用问题?
  • 最简单的选择是在 if/else 块之前分配给变量。
  • 只是为了澄清人们的意思和“短路” - 如果 AND 语句的第一部分为 False,则永远不会评估其余部分,因为我们已经可以知道结果是什么。与 or 和 True 值相同 - 第一个 True 值足以说明整个 OR 将为 True。如果您无论如何都计算了所有变量,则需要事先创建它们。
  • 有趣的问题,但请接受@khelwood 的建议。即使在这个简单的示例中,我也很难看出这将如何提高代码的清晰度。

标签: python python-3.x walrus-operator


【解决方案1】:

logical operator and 仅有条件地评估其第二个操作数。没有正确的方法来进行无条件需要的条件赋值。

改为使用"binary" operator &,它无条件地计算第二个操作数。

arr = []
for i in range(1, 25):
    #                        v force evaluation of both operands
    if (three := i % 3 == 0) & (five := i % 5 == 0):
        arr.append("FizzBuzz")
    elif three:
        arr.append("Fizz")
    elif five:
        arr.append("Buzz")
    else:
        arr.append(str(i))

print(arr)
# ['1', '2', 'Fizz', '4', 'Buzz', 'Fizz', '7', '8', 'Fizz', 'Buzz', '11', ...]

相应地,可以使用| 作为or 的无条件变体。此外,“xor”运算符^ 根本没有条件评估的等价物。

值得注意的是,二元运算符将布尔值评估为纯布尔值 - 例如,False | TrueTrue 而不是 1 - 但对于其他类型可能会有所不同。要使用二元运算符在布尔上下文中评估任意值(例如 lists),请在赋值后将它们转换为 bool

#  |~~~ force list to boolean ~~| | force evaluation of both operands
#  v    v~ walrus-assign list ~vv v
if bool(lines := list(some_file)) & ((today := datetime.today()) == 0):
   ...

由于赋值表达式需要括号来获得适当的优先级,逻辑(andor)和二进制(&|^)运算符之间的常见问题different precedence 在这里无关紧要。

【讨论】:

  • 由于&and 的位操作,这仅适用于threefive 的分配产生重叠位的一些结果。 True 满足这一点,但如果分配的值没有匹配的位,可能会出现意外。 bool(3&4)False,例如 bool(3 and 4)True
【解决方案2】:

您遇到的问题是 five 仅在此语句中 three 为 True 时分配,因为 short circuiting

if (three:= i%3==0) and (five:= i%5 ==0)

所以five 通常未被分配,导致NameError 或使用非当前值。

您可以通过在其中形成一个包含海象分配的非空元组,然后在该元组之后使用您所期望的 threefive 来强制一个 True 值。

这并不比在if 之前分配threefive 更漂亮,但这有效:

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']

任何非空元组在 Python 中都是 True。形成它会导致(three:=i%3==0, five:=i%5==0) 始终是真实的,并且每次都分配三个和五个。由于该元组为真,因此必须使用正确的值 3 和 5 来评估表达式的其余部分。

或者,使用if all((three:=i%3==0, five:=i%5==0)):,因为元组是在测试其内容之前形成的——即使all短路;这只会在元组形成后发生。

这些形式中的任何一种都可以轻松重构为理解:

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)]

或者,

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)]

如果 each 元素的结果不是布尔值,请注意构造 if (three := i % 3 == 0) & (five := i % 5 == 0):。你可能会遇到一些意想不到的失败:

>>> bool((x:=3) & (y:=4))
False
>>> bool((x:=3) and (y:=4))
True   

解决此问题的唯一方法是将bool 应用于每个:

>>> bool(x:=3) & bool(y:=4)
True

顺便说一句,说到元组,一种在 Python 中进行 FizzBu​​zz 类型挑战的更短的方法:

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)]

如果你正在寻找新的东西,这种类型的问题对于 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)   

【讨论】:

    猜你喜欢
    • 2022-11-24
    • 2020-12-31
    • 2011-05-15
    • 2011-01-19
    • 1970-01-01
    • 1970-01-01
    • 2021-06-20
    • 1970-01-01
    • 2014-04-03
    相关资源
    最近更新 更多