Как переписать этот простой цикл, используя выражения присваивания, представленные в 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 есть раздел с рекомендациями по стилю, который вы должны учитывать:

  1. Если можно использовать операторы присваивания или выражения присваивания, предпочтите операторы; они являются четкой декларацией о намерениях.
  2. Если использование выражений присваивания приведет к неоднозначности порядка выполнения, реструктурируйте его, чтобы использовать вместо него операторы.

Простые циклы должны быть реализованы с использованием итераций и 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
Другие вопросы по тегам