python - 如何使用 Python 3.8 alpha 中引入的赋值表达式重写这个简单的循环?
问题描述
在我看来,将经典的 while 循环与赋值表达式互换并不是那么简单循环交换以保持代码看起来很棒并不是那么简单。
考虑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
)
解决方案
Simple loops like your example should not be using assignment expressions. The PEP has a Style guide recommendations section that you should heed:
- If either assignment statements or assignment expressions can be used, prefer statements; they are a clear declaration of intent.
- If using assignment expressions would lead to ambiguity about execution order, restructure it to use statements instead.
Simple loops should be implemented using iterables and for
, they are much more clearly intended to loop until the iterator is done. For your example, the iterable of choice would be range()
:
for a in range(10):
# ...
which is far cleaner and concise and readable than, say
a = -1
while (a := a + 1) < 10:
# ...
The above requires extra scrutiny to figure out that in the loop a
will start at 0
, not at -1
.
The bottom line is that you should not be tempted to 'find ways to use assignment statements'. Use an assignment statement only if it makes code simpler, not more complex. There is no good way to make your while
loop simpler than a for
loop here.
Your attempts at rewriting a simple loop are also echoed in the 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 (among many other great contributions to Python and software engineering as a whole), so his words should carry some extra weight:
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.
Bold emphasis mine.
A much better use-case for assignment expressions is the assigment-then-test pattern, especially when multiple tests need to take place that try out successive objects. Tim's essay quotes an example given by Kirill Balunov, from the standard library, which actually benefits from the new syntax. The copy.copy()
function has to find a suitable hook method to create a copy of a custom object:
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)
The indentation here is the result of nested if
statements because Python doesn't give us a nicer syntax to test different options until one is found, and at the same time assigns the selected option to a variable (you can't cleanly use a loop here as not all tests are for attribute names).
But an assignment expression lets you use a flat if / elif / else
structure:
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)
Those 8 lines are a lot cleaner and easier to follow (in my mind) than the current 13.
Another often-cited good use-case is the if there is a matching object after filtering, do something with that object, which currently requires a next()
function with a generator expression, a default fallback value, and an if
test:
found = next((ob for ob in iterable if ob.some_test(arg)), None)
if found is not None:
# do something with 'found'
which you can clean up a lot with the any()
function
if any((found := ob).some_test(arg) for ob in iterable):
# do something with 'found'
推荐阅读
- c - 在通用函数中释放特定类型的数据/访问冲突读取错误位置
- javascript - 用于添加、删除、调整大小和更改列顺序的 DataTables 表的选项
- npm - ng new 不起作用,显示空白终端屏幕
- javascript - 如何在全局函数中创建 js var 以在外部使用它?
- python - pythonic方式来初始化实例变量
- apache-kafka - Kafka 超时异常:批次已过期
- java - 嵌套滚动视图不滚动到活动底部
- php - 使用php更改按字符分隔的字符串的位置
- python - 使用opencv填充空白区域并删除图像区域中的突出区域
- c++ - 通过 Rcpp 中的索引求和