如何使用Python 3.8 alpha中引入的赋值表达式来重写此简单循环?

时间:2019-03-22 17:42:14

标签: python python-3.8 python-assignment-expression

在我看来,将经典的while循环与assignment-expressions循环互换以保持代码美观并不那么简单。

考虑example1

>>> a = 0
>>> while (a := a+1) < 10:
...     print(a)
... 
1
2
3
4
5
6
7
8
9

example2

>>> a = 0
>>> while a < 10:
...     print(a)
...     a += 1
... 
0
1
2
3
4
5
6
7
8
9

您将如何修改example1才能获得与0相同的输出(不跳过example2)? (当然,无需更改a = 0

3 个答案:

答案 0 :(得分:17)

像您的示例这样的简单循环不应使用赋值表达式。 PEP有一个Style guide recommendations section,请注意:

  
      
  1. 如果可以使用赋值语句或赋值表达式,则首选语句;它们是意图的明确声明。
  2.   
  3. 如果使用赋值表达式会导致执行顺序含糊不清,请将其重组为使用语句。
  4.   

简单的循环应该使用iterables和for来实现,它们显然要循环进行直到迭代器完成。对于您的示例,选择的可迭代项为range()

for a in range(10):
    # ...

比说的

更简洁,可读性更强
a = -1
while (a := a + 1) < 10:
    # ...

上面的内容需要进行额外的审查才能确定在循环中 a将从0开始,而不是-1

最重要的是,您不应被诱使“寻找使用赋值语句的方法”。仅当赋值语句使代码更简单而不复杂时才使用赋值语句。没有比这里的while循环更简单的for循环的好方法。

Tim Peters's findings appendix也呼应了您尝试重写简单循环的尝试,其中引用了蒂姆·彼得斯关于样式和赋值表达式的内容。蒂姆·彼得斯(Tim Peters)是Zen of Python的作者(在对Python和整个软件工程的许多其他伟大贡献中),因此他的话应具有额外的分量:

  

在其他情况下,将相关的逻辑结合起来就很难理解,例如重写:

while True:
    old = total
    total += term
    if old == total:
        return total
    term *= mx2 / (i*(i+1))
    i += 2
     

作为简报人:

while total != (total := total + term):
    term *= mx2 / (i*(i+1))
    i += 2
return total
     

while测试太微妙了,关键是在非短路或方法链接的情况下严格依赖从左到右的评估。我的大脑没有那样连线。

强调粗体。

分配表达式的更好用例是 assigment-then-test 模式,尤其是在需要进行多个测试以尝试连续对象的测试时。 Tim的文章引用了标准库中Kirill Balunov给出的示例,该示例实际上受益于新语法。 copy.copy() function必须find a suitable hook method才能创建自定义对象的副本:

reductor = dispatch_table.get(cls)
if reductor:
    rv = reductor(x)
else:
    reductor = getattr(x, "__reduce_ex__", None)
    if reductor:
        rv = reductor(4)
    else:
        reductor = getattr(x, "__reduce__", None)
        if reductor:
            rv = reductor()
        else:
            raise Error("un(shallow)copyable object of type %s" % cls)

此处的缩进是嵌套if语句的结果,因为Python在找到一个选项之前不会为我们提供更好的语法来测试不同的选项,同时将选定的选项分配给一个变量(您可以不能干净地使用循环,因为并非所有测试都针对属性名称。

但是赋值表达式使您可以使用 flat if / elif / else结构:

if reductor := dispatch_table.get(cls):
    rv = reductor(x)
elif reductor := getattr(x, "__reduce_ex__", None):
    rv = reductor(4)
elif reductor := getattr(x, "__reduce__", None):
    rv = reductor()
else:
    raise Error("un(shallow)copyable object of type %s" % cls)

在我看来,这8行比现在的13行更简洁,更容易理解。

另一个经常被引用的良好用例是,如果在过滤后存在匹配的对象,请对该对象进行处理,该对象目前需要带有生成器表达式的next() function,默认后备值和if测试:

found = next((ob for ob in iterable if ob.some_test(arg)), None)
if found is not None:
    # do something with 'found'

您可以使用any() function

进行很多清理
if any((found := ob).some_test(arg) for ob in iterable):
    # do something with 'found'

答案 1 :(得分:0)

我建议执行do-while循环,但Python不支持。虽然,您可以模仿片刻。通过这种方式,您可以使用赋值表达式

a=0
while True:
    print(a)
    if not((a:=a+1)<10):
        break

答案 2 :(得分:0)

这个问题的问题是,解决该问题的整个方法似乎来自试图像其他语言一样使用Python的程序员。

在这种情况下,经验丰富的Python程序员不会使用while循环。他们要么这样做,要么:

from itertools import takewhile, count

for a in takewhile(lambda x: x<10, count()):
    print (a)

...或更简单:

for a in range (10):
    print (a)

在这种情况下(当然并非总是如此),问题中出现的难看代码是使用语言的一种征兆,而不是最佳方式。