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

How to rewrite this simple loop using assignment expressions introduced in Python 3.8 alpha?

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

考虑 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 以获得与 example2 相同的输出(不跳过 0)? (当然没有改变a = 0

我建议使用 do-while 循环,但 Python 不支持它。虽然,您可以模拟 while 充当 do-while。通过这种方式,您可以使用赋值表达式

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

这个问题的问题是,解决问题的整个方法似乎来自于试图像其他语言一样使用 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)

通常情况下(当然不总是),问题中出现的丑陋代码是未以最佳方式使用该语言的症状。

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

  1. If either assignment statements or assignment expressions can be used, prefer statements; they are a clear declaration of intent.
  2. If using assignment expressions would lead to ambiguity about execution order, restructure it to use statements instead.

简单的循环应该使用迭代器和 for 来实现,它们更明确地旨在循环直到迭代器完成。对于您的示例,选择的迭代将是 range():

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

更清晰、简洁和可读
a = -1
while (a := a + 1) < 10:
    # ...

以上需要额外的检查才能弄清楚 在循环中 a 将从 0 开始,而不是 -1

底线是你不应该受到诱惑 'find ways to use assignment statements'。仅当它使代码更简单而不是更复杂时才使用赋值语句。没有什么好的方法可以使您的 while 循环比此处的 for 循环更简单。

您重写一个简单循环的尝试也反映在 Tim Peters's findings appendix, which quotes Tim Peters on the subject of style and assignment expressions. Tim Peters is the author of the Zen of Python 中(在对 Python 和整个软件工程的许多其他伟大贡献中),所以他的话应该有一些额外的分量:

In other cases, combining related logic made it harder to understand, such as rewriting:

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

as the briefer:

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

The while test there is too subtle, crucially relying on strict left-to-right evaluation in a non-short-circuiting or method-chaining context. My brain isn't wired that way.

大胆强调我的。

赋值表达式的一个更好的用例是 赋值然后测试 模式,尤其是当需要进行多个测试以尝试连续的对象时。 Tim 的文章引用了标准库中 Kirill Balunov 给出的示例,该示例实际上受益于新语法。 copy.copy() function has to 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'