Как переписать этот простой цикл, используя выражения присваивания, представленные в Python 3.8 alpha?
Мне кажется, что не очень просто обмениваться классическими циклами while с выражениями- присваиваниями -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
для того, чтобы иметь тот же вывод (не пропуская 0
) из example2
? (без изменения a = 0
, конечно)
3 ответа
Простые циклы, подобные вашему примеру, не должны использовать выражения присваивания. В PEP есть раздел с рекомендациями по стилю, который вы должны учитывать:
- Если можно использовать операторы присваивания или выражения присваивания, предпочтите операторы; они являются четкой декларацией о намерениях.
- Если использование выражений присваивания приведет к неоднозначности порядка выполнения, реструктурируйте его, чтобы использовать вместо него операторы.
Простые циклы должны быть реализованы с использованием итераций и for
, они гораздо более четко предназначены для зацикливания, пока итератор не закончится. Для вашего примера, итерация выбора будет range()
:
for a in range(10):
# ...
который намного чище, лаконичнее и читабельнее, чем, скажем,
a = -1
while (a := a + 1) < 10:
# ...
Вышеуказанное требует дополнительной проверки, чтобы выяснить, что в цикле a
начнется в 0
не в -1
,
Суть в том, что вы не должны поддаваться искушению "найти способы использовать операторы присваивания". Используйте оператор присваивания, только если он делает код проще, а не сложнее. Там нет хорошего способа сделать ваш while
цикл проще, чем for
Цикл здесь.
Ваши попытки переписать простой цикл также отражены в приложении к выводам Тима Петерса, в котором цитируются слова Тима Питерса о стиле и выражениях присваивания. Тим Питерс является автором 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
тест слишком тонкий, в основном полагаясь на строгую оценку слева направо в контексте короткого замыкания или цепочки методов. Мой мозг не подключен таким образом.
Жирный акцент мой.
Гораздо лучшим вариантом использования выражений присваивания является шаблон " задание-потом-тест", особенно когда необходимо выполнить несколько тестов, которые опробуют последовательные объекты. В эссе Тима цитируется пример, приведенный Кириллом Балуновым из стандартной библиотеки, который фактически выигрывает от нового синтаксиса. 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)
Отступ здесь является результатом вложенного 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)
Эти 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'
который вы можете очистить много с any()
функция
if any((found := ob).some_test(arg) for ob in iterable):
# do something with 'found'
Проблема в том, что весь подход к этой проблеме, похоже, исходит от программиста, пытающегося использовать 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)
Как это часто бывает (не всегда, конечно), уродливый код, представленный в вопросе, является симптомом использования языка менее чем оптимальным способом.
Я бы предложил цикл do-while, но он не поддерживается в Python. Хотя, вы можете подражать какое-то время, действуя как пока. Таким образом, вы можете использовать выражение присваивания
a=0
while True:
print(a)
if not((a:=a+1)<10):
break