": =" выражения синтаксиса и присваивания: что и почему?

PEP 572 представляет выражения присваивания (в народе известные как оператор Моржа), реализованные для Python 3.8. Это кажется действительно существенной новой функцией, поскольку она позволяет использовать эту форму назначения в пределах функций понимания и лямбда-функций.

Что такое синтаксис, семантика и грамматическая спецификация выражений присваивания?

Почему вводится эта новая (и, казалось бы, довольно радикальная концепция), когда аналогичная идея в PEP 379 о "Добавление выражения присваивания" была ранее отклонена?

Ответ 1

PEP 572 содержит множество деталей, особенно по первому вопросу. Я постараюсь кратко резюмировать/процитировать некоторые из наиболее важных частей PEP:

Обоснование

Разрешение этой формы присваивания внутри пониманий, таких как списки и лямбда-функции, где традиционные присвоения запрещены. Это также может облегчить интерактивную отладку без необходимости рефакторинга кода.

Синтаксис и семантика

В любом контексте, где могут использоваться произвольные выражения Python, может появляться именованное выражение. Это имеет форму name := expr, где expr - любое допустимое выражение Python, а name - идентификатор.

Значение такого именованного выражения совпадает со встроенным выражением, но с дополнительным побочным эффектом назначению целевому значению этого значения

Отличия от регулярных операторов присваивания

Помимо выражения, а не оператора, в PEP упоминается несколько отличий: назначения выражений идут справа налево, имеют разный приоритет относительно запятых и не поддерживают:

  • Несколько целей
x = y = z = 0  # Equivalent: (z := (y := (x := 0)))
  • Назначения не для одного имени:
# No equivalent
a[i] = x
self.rest = []
  • Повторяемая упаковка/распаковка
# Equivalent needs extra parentheses

loc = x, y  # Use (loc := (x, y))
info = name, phone, *rest  # Use (info := (name, phone, *rest))

# No equivalent

px, py, pz = position
name, phone, email, *other_info = contact
  • Встроенные аннотации типа:
# Closest equivalent is "p: Optional[int]" as a separate declaration
p: Optional[int] = None
  • Расширенное назначение не поддерживается:
total += tax  # Equivalent: (total := total + tax)

Рекомендуемые варианты использования

а) Упрощение списка понимания

например:

stuff = [(lambda y: [y,x/y])(f(x)) for x in range(5)]

может стать:

stuff = [[y := f(x), x/y] for x in range(5)]

б) Получение условных значений

например (в Python 3):

command = input("> ")
while command != "quit":
    print("You entered:", command)
    command = input("> ")

может стать:

while (command := input("> ")) != "quit":
    print("You entered:", command)

Ответ 2

Несколько моих любимых примеров того, как выражения присваивания могут сделать код более кратким и легким для чтения:

if выражение

До:

match = pattern.match(line)
if match:
    return match.group(1)

После:

if match := pattern.match(line):
    return match.group(1)

Бесконечное while утверждение

До:

while True:
    data = f.read(1024)
    if not data:
        break
    use(data)

После:

while data := f.read(1024):
    use(data)

В ПКП есть и другие хорошие примеры.

Ответ 3

Еще несколько примеров и обоснований теперь, когда 3.8 был официально выпущен.

Наименование результата выражения является важной частью программирования, позволяя использовать описательное имя вместо более длинного выражения и разрешать повторное использование. В настоящее время эта функция доступна только в форме выписки, что делает ее недоступной в списках и других контекстах выражений.

Источник: LicensedProfessional reddit comment

Обрабатывать подходящее регулярное выражение

if (match := pattern.search(data)) is not None:
    # Do something with match

Цикл, который нельзя переписать с помощью 2-arg iter()

while chunk := file.read(8192):
   process(chunk)

Повторно использовать значение, которое дорого вычислять

[y := f(x), y**2, y**3]

Совместное использование подвыражения между предложением фильтра понимания и его выводом

filtered_data = [y for x in data if (y := f(x)) is not None]