Как переписать этот простой цикл, используя выражения присваивания, представленные в Python 3.8 alpha? - PullRequest
4 голосов
/ 22 марта 2019

Мне кажется, что не очень просто обменивать классические циклы 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, конечно)

Ответы [ 3 ]

17 голосов
/ 27 марта 2019

Простые циклы, подобные вашему примеру , не должны использовать выражения присваивания .У 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 также существуеттонкий, в значительной степени опирающийся на строгую оценку слева направо в контексте короткого замыкания или цепочки методов.Мой мозг не подключен таким образом.

Мой жирный акцент.

Гораздо лучший вариант использования для выражений присваивания - assigment-then-testШаблон , особенно когда необходимо выполнить несколько тестов для проверки последовательных объектов.В эссе Тима цитируется пример, приведенный Кириллом Балуновым из стандартной библиотеки, который фактически выигрывает от нового синтаксиса.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 не дает нам более приятного синтаксиса для тестирования различных опций, пока один не найден, и в то же время назначает выбранную опцию переменной (вы не можете использовать цикл здесь чисто, так как не все тестыдля имен атрибутов).

Но выражение присваивания позволяет использовать структуру 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() с выражением генератора, запасное значение по умолчанию и тест 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'
0 голосов
/ 27 марта 2019

Проблема в том, что весь подход к проблеме, похоже, исходит от программиста, пытающегося использовать 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)

Как это часто бывает (не всегда, конечно), уродливый кодВ этом вопросе симптом использования языка менее чем оптимальным способом.

0 голосов
/ 26 марта 2019

Я бы предложил цикл do-while, но он не поддерживается в Python. Хотя, вы можете подражать какое-то время, действуя как пока. Таким образом, вы можете использовать выражение присваивания

a=0
while True:
    print(a)
    if not((a:=a+1)<10):
        break
...