在我看来,将经典的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
)
答案 0 :(得分:17)
像您的示例这样的简单循环不应使用赋值表达式。 PEP有一个Style guide recommendations section,请注意:
- 如果可以使用赋值语句或赋值表达式,则首选语句;它们是意图的明确声明。
- 如果使用赋值表达式会导致执行顺序含糊不清,请将其重组为使用语句。
简单的循环应该使用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)
在这种情况下(当然并非总是如此),问题中出现的难看代码是使用语言的一种征兆,而不是最佳方式。