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

ale*_*inn 5 python python-3.8 python-assignment-expression

在我看来,将经典的while循环与Assignment-expressions -loops互换可以使代码看起来很棒不是那么简单。

考虑example1

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

example2

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

您将如何修改example1以具有相同的输出(不跳过0example2?(a = 0当然,无需更改)

Mar*_*ers 17

像您的示例这样的简单循环不应使用赋值表达式。PEP有一个样式指南建议部分,您应注意以下几点:

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

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

for a in range(10):
    # ...
Run Code Online (Sandbox Code Playgroud)

这是远更清洁和简明易读比,也就是说

a = -1
while (a := a + 1) < 10:
    # ...
Run Code Online (Sandbox Code Playgroud)

上面需要进行额外的检查,才能确定循环中的 a起始于0,而不是起始于-1

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

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

在其他情况下,将相关的逻辑组合在一起会更难理解,例如重写:

while True:
    old = total
    total += term
    if old == total:
        return total
    term *= mx2 / (i*(i+1))
    i += 2
Run Code Online (Sandbox Code Playgroud)

作为简介:

while total != (total := total + term):
    term *= mx2 / (i*(i+1))
    i += 2
return total
Run Code Online (Sandbox Code Playgroud)

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

大胆强调我的。

赋值表达式的一个更好的用例是“先贴后测试”模式,尤其是在需要进行尝试连续对象的多个测试时。Tim的文章引用了标准库中Kirill Balunov给出的示例,该示例实际上受益于新语法。该copy.copy()函数必须找到合适的钩子方法来创建自定义对象的副本:

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)
Run Code Online (Sandbox Code Playgroud)

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

但是赋值表达式使您可以使用平面 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)
Run Code Online (Sandbox Code Playgroud)

这8条线比目前的13条线更干净,更容易(在我看来)。

另一个经常被引用的好用例是:过滤后是否存在匹配的对象,对该对象做点什么,当前需要具有生成器表达式,默认后备值和测试的next()函数if

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

你可以用这个功能清理很多any()

if any((found := ob).some_test(arg) for ob in iterable):
    # do something with 'found'
Run Code Online (Sandbox Code Playgroud)