Зачем в инструкции присваивания используются скобки

Введение в программирование

49

Инструкция присваивания

Инструкция присваивания является основной вычислительной инструкцией. В результате выполнения инструкции присваивания значение переменной меняется, ей присваивается новое значение.

Примеры:

Sum = Cena * Kol

Kg = 0.495 * Funt Total = 0

В общем виде инструкция присваивания записывается так:

Переменная = Выражение

Здесь:

Переменная — переменная, значение которой надо изменить

(присвоить значение);

= — оператор «присвоить»;

Выражение — выражение, значение которого надо записать в переменную.

Выполняется инструкция присваивания следующим образом: сначала вычисляется значение выражения, указанного справа от символа присваивания (=), затем полученное значение записывается в переменную, имя которой указано слева от символа присваивания.

Например, в результате выполнения инструкций:

i = 0

значение переменной i становится равным нулю;

a = b + c

значение переменной a становится равным сумме

значений переменных b и c;

n = n + 1

значение переменной n увеличивается на единицу.

Выражение

Выражение состоит из операндов и операторов. Операнды, в качестве которых могут выступать константы, переменные, а также функции — это объекты, над которыми выполняются действия. Операторы (табл. 3.1) обозначают действия, выполняемые над операндами.

Таблица 3.1. Операторы

Оператор Действие

+Сложение

Вычитание

*Умножение

/Деление

Простое выражение состоит из двух операндов и находящегося между ними оператора.

Например:

i + 1 Cena * Kol

sum — discount cena * 0.05

U / R

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

Например:

0.05 K

0

Введение в программирование

51

При вычислении значения выражения следует учитывать порядок выполнения действий. В общем случае действует известное правило вычислений значений выражений: сначала выполняется умножение и деление, затем — сложение и вычитание. Другими словами, сначала выполняются действия, соответствующие операторам, имеющим более высокий приоритет (*, /), затем более низкий (+, ). Если приоритет операторов одинаков, то сначала выполняется действие, соответствующее оператору, находящемуся левее.

Для задания требуемого порядка выполнения операторов в выражении можно использовать скобки. Например:

(a + b) / 2

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

При записи выражений, содержащих скобки, должна соблюдаться парность скобок, т. е. число открывающих скобок должно быть равно числу закрывающих скобок. Нарушение парности скобок — наиболее распространенная ошибка при записи выражений.

Контрольные вопросы

Запишите инструкцию присваивания, обеспечивающую вычисление:

площади прямоугольника;

величины силы тока в электрической цепи;

стоимости печати фотографий;

дохода по вкладу в банке;

увеличения на единицу значения переменной N.

При написании программного кода одной из самых важных задач программиста является правильная работа с переменными. Одно из ключевых понятий в данном контексте — это присваивание значения переменной. Для выполнения этой операции в многих языках программирования требуется использование скобок.

Зачем же в инструкции присваивания используются скобки? Ответ на этот вопрос заключается в двух основных моментах. Во-первых, скобки служат для указания порядка выполнения операций. Они могут быть использованы для группировки операции и подчеркивания того, что она должна быть выполнена в первую очередь.

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

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

Содержание

  1. Зачем нужны скобки в инструкции присваивания?
  2. Улучшение читаемости кода
  3. Определение порядка выполнения операций
  4. Обеспечение ясности и точности кода

Зачем нужны скобки в инструкции присваивания?

Скобки в инструкции присваивания играют важную роль в программировании. Они позволяют указать порядок выполнения операций и разрешают использование сложных выражений в правой части присваивания.

Один из основных случаев, когда нужно использовать скобки, — это изменение порядка выполнения операций. Например, инструкция x = 3 * 4 + 5 выполнится по правилам математических операций, сначала произведется умножение, а затем сложение. Однако, если вы хотите, чтобы сложение было выполнено раньше умножения, вам необходимо использовать скобки: x = (3 * 4) + 5. Таким образом, скобки позволяют контролировать порядок выполнения операций и гарантировать получение правильного результата.

Еще одним случаем, когда скобки являются неотъемлемой частью инструкции присваивания, является использование сложных выражений в правой части присваивания. Например, вы можете использовать арифметические операции, функции, логические выражения и многое другое. Использование скобок позволяет явно указать, какая часть выражения должна быть вычислена первой. Например, x = (2 + 3) * (4 — 1) обозначает, что сначала нужно выполнить вычисление внутри скобок, а затем умножить результат на вычисление второй части выражения. Без скобок данное выражение было бы неверным и привело бы к неправильному результату.

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

Улучшение читаемости кода

Использование скобок в инструкциях присваивания играет важную роль в программировании. Они позволяют улучшить читаемость кода и сделать его более понятным для разработчика.

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

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

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

Таким образом, использование скобок в инструкциях присваивания не только обеспечивает правильность выполнения операций, но и повышает читаемость кода. Код, в котором явно указан порядок операций, легче понимать и поддерживать, что является важным аспектом в разработке программного обеспечения.

Определение порядка выполнения операций

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

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

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

Наличие скобок в инструкции присваивания позволяет явно определить порядок выполнения операций. Если в инструкции присваивания используются скобки, то операции внутри скобок будут выполнены раньше, чем операции вне скобок.

Например, в следующей инструкции с использованием скобок: result = (x + y) * z; сначала будет выполнена операция сложения x + y, а затем операция умножения результата на z.

Если бы скобки не были использованы: result = x + y * z;, то операция умножения была бы выполнена раньше, чем операция сложения, и результат был бы отличным от ожидаемого.

Правильное использование скобок в программировании помогает установить ясный порядок выполнения операций и получить правильный результат вычислений.

Обеспечение ясности и точности кода

Использование скобок в инструкции присваивания имеет важное значение для обеспечения ясности и точности кода. Установка приоритетов и группировка операций с помощью скобок позволяет избежать неоднозначностей и предотвращает ошибки интерпретации кода.

Одна из основных причин использования скобок в инструкции присваивания заключается в иерархии операций. Если мы хотим, чтобы определенные операции были выполнены перед другими, мы можем использовать скобки для установки приоритетов. Например, в выражении (a + b) * c операция сложения будет выполнена первой, а затем результат будет умножен на значение c. Если бы скобки не были использованы, операции выполнялись бы в порядке слева направо: a + (b * c).

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

Кроме того, скобки могут использоваться для группировки операций. Например, в выражении (a + b) * (c + d) мы имеем две группы операций: (a + b) и (c + d). Группировка операций с помощью скобок повышает читабельность кода, позволяет быстрее понять его назначение и намерения программиста.

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

Привет, Хабр. В этот раз мы рассмотрим PEP 572, который рассказывает про выражения присваивания. Если Вы до сих пор скептически относитесь к оператору «:=» или не до конца понимаете правила его использования, то эта статья для Вас. Здесь вы найдёте множество примеров и ответов на вопрос: «Почему именно так?». Эта статья получилась максимально полной и если у Вас мало времени, то просмотрите раздел, написанный мной. В его начале собраны основные «тезисы» для комфортной работы с выражениями присваивания.

PEP 572 — Выражения Присваивания

Содержание

  • Аннотация
  • Обоснование
  • Синтаксис и семантика
  • Спецификация изменяется во время реализации
  • Примеры
  • Отклоненные альтернативны
  • Частые возражения
  • Рекомендации по стилю
  • Благодарность
  • Приложение A: выводы Тима Петерса
  • Приложение B: Грубый интерпретатор кода для генераторов
  • Приложение C: Никаких изменений в семантике области видимости
  • Ссылки
  • Авторские права
  • Моя часть

Аннотация

Это соглашение расскажет о появившейся возможности присваивания внутри выражений, с помощью нового обозначения NAME := expr.

В рамках нововведений был обновлен порядок вычисления генераторов словарей (dictionary comprehension). Это гарантирует, что выражение ключа вычислится перед выражением значения (это позволяет привязывать ключ к переменной, а затем повторно использовать созданную переменную в вычислении значения, соответствующего ключу).

Во время обсуждения этого PEP, данный оператор стал неофициально известен как «моржовый оператор» (the walrus operator). Формальное имя конструкции — «Выражение присваивания» (согласно заголовку PEP: Assignment Expressions), но она может упоминаться, как «Именованные выражения» (Named Expressions). Например, эталонная реализация в CPython использует именно это название.

Обоснование

Именование является важной частью программирования, которая позволяет использовать «описательное» имя вместо более длинного выражения, а также упрощает повторное использование значений. В настоящее время это возможно сделать лишь в виде инструкции, что делает эту операцию недоступной при генерации списков (list comprehension), а также в других выражениях.

Кроме того, именование частей большого выражения может помочь при интерактивной отладке, предоставив инструменты отображения подсказок и промежуточных результатов. Без возможности захвата результатов вложенных выражений, потребуется изменение исходного кода, но используя выражения присваивания вам достаточно вставить несколько «маркеров» вида «имя := выражение». Это устраняет лишний рефакторинг, а значит снижает вероятность непреднамеренного изменения кода в процессе отладки (частая причина Heisenbugs [прим. гейзенбаги — ошибки, которые меняют свойства кода во время отладки и могут неожиданно проявиться в продакшене] ), а также данный код будет более понятен другому программисту.

Важность реального кода

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

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

Тем не менее, для таких примеров есть хорошее применение: они помогают уточнить предполагаемую семантику. Поэтому мы приведём ниже некоторые из них. Однако, чтобы быть убедительными, примеры должны основываться на реальном коде, который был написан без размышлений об этом PEP-е. То есть код, являющийся частью реально полезного приложения (без разницы: большое оно, или маленькое). Тим Питерс очень сильно помог нам, просмотрев свои личные репозитории и выбрав примеры написанного им кода, которые (по его мнению) стали бы более понятным, если их переписать (без фанатизма) с использованием выражений присваивания. Его вывод таков: текущие изменения привнесли бы скромное, но явное улучшение в нескольких битах его кода.

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

Показательный случай: Гвидо нашел несколько иллюстративных моментов, когда программист повторяет подвыражение (тем самым замедляя программу), но экономит лишнюю строку кода. Например, вместо того, чтобы писать:

match = re.match(data)
group = match.group(1) if match else None

Программисты предпочитали такой вариант:

group = re.match(data).group(1) if re.match(data) else None

Вот ещё пример, показывающий, что программисты иногда готовы сделать больше работы, чтобы сохранить «прежний уровень» отступов:

match1 = pattern1.match(data)
match2 = pattern2.match(data)
if match1:
    result = match1.group(1)
elif match2:
    result = match2.group(2)
else:
    result = None

Этот код вычисляет pattern2, даже если pattern1 уже совпал (в этом случае второе под-условие никогда не выполнится). Поэтому следующее решение является более эффективным, но менее привлекательным:

match1 = pattern1.match(data)
if match1:
    result = match1.group(1)
else:
    match2 = pattern2.match(data)
    if match2:
        result = match2.group(2)
    else:
        result = None

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

В большинстве случаев, где в Python используются произвольные выражения (arbitrary expressions), теперь можно применять выражения присваивания. Они имеют форму NAME := expr, где expr — любое допустимое выражение Python, кроме кортежа без скобок (unparenthesized tuple), а NAME — идентификатор. Значение такого выражения совпадает с исходным, но дополнительным эффектом является присвоение значения целевому объекту:

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

# A loop that can't be trivially rewritten using 2-arg iter()
while chunk := file.read(8192):
   process(chunk)

# Reuse a value that's expensive to compute
[y := f(x), y**2, y**3]

# Share a subexpression between a comprehension filter clause and its output
filtered_data = [y for x in data if (y := f(x)) is not None]

Исключительные случаи

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

  • Выражения присваивания, не заключённые в скобки, запрещены на «верхнем» уровне:
    y := f(x)  # НЕДОПУСТИМО
    (y := f(x))  # Сработает, но не рекомендуется

    Это правило упростит программисту выбор между оператором присваивания и выражением присваивания — не будет существовать синтаксической ситуации, в которой оба варианта равноценны.

  • Не заключенные в скобки выражения присваивания запрещены в правой части каскадного присваивания. Пример:
    y0 = y1 := f(x)  # НЕДОПУСТИМО
    y0 = (y1 := f(x))  # Сработает, но не рекомендуется

    Не заключенные в скобки выражения присваивания запрещены в значениях ключевого аргумента при вызове функции. Пример:

    foo(x = y := f(x))  # НЕДОПУСТИМО
    foo(x=(y := f(x)))  # Возможно, хотя и сбивает с толку

    Опять же, такой механизм уменьшает у людей желание окончательно запутать и так сложный синтаксический анализ ключевых аргументов.

  • Не заключенные в скобки выражения присваивания запрещены в значениях аргумента по умолчанию. Пример:
    def foo(answer = p := 42):  # НЕДОПУСТИМО
        ...
    def foo(answer=(p := 42)):  # Valid, though not great style
        ...

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

  • Не заключенные в скобки выражения присваивания запрещены в качестве аннотаций для аргументов, возвращаемых значений и присваиваний. Пример:
    def foo(answer: p := 42 = 5):  # НЕДОПУСТИМО
        ...
    def foo(answer: (p := 42) = 5):  # Разрешено, но бесполезно
        ...

    Рассуждения по поводу введения этого правила аналогичны предыдущим: код, состоящий из комбинации операторов «=» и «:=» трудно правильно понять.

  • Не заключенные в скобки выражения присваивания запрещены в лямбда-функциях. Пример:
    (lambda: x := 1) # НЕДОПУСТИМО
    lambda: (x := 1) # Разрешено, но бесполезно
    (x := lambda: 1) # Разрешено
    lambda line: (m := re.match(pattern, line)) and m.group(1) # Valid

    Лямбда-функция имеет приоритет более высокий, чем «:=». Удобное присваивание лямбды к переменной здесь важнее. В случаях, когда переменная используется несколько раз, вам и так (наверняка) понадобятся скобки, потому это ограничение не сильно повлияет на ваш код.

  • Выражения присваивания внутри f-строк требуют скобок. Пример:
    >>> f'{(x:=10)}'  # Разрешено, выражение присваивания
    '10'
    >>> x = 10
    >>> f'{x:=10}'    # Разрешено, будет отформатировано, как '=10'
    '        10'

    Это показывает, что не всё выглядящее, как оператор присваивания в f-строке, является таковым. Парсер f-строки использует символ «:» для указания параметров форматирования. Чтобы сохранить обратную совместимость, при использовании оператора присваивания внутри f-строк он должен быть заключен в скобки. Как отмечено в примере выше, такое использование оператора присваивания не рекомендуется.

Область видимости

Выражение присваивания не вводит новую область видимости. В большинстве случаев область видимости, в которой будет создана переменная, не требует пояснений: она будет текущей. Если прежде переменная использовала ключевые слова nonlocal или global, тогда выражение присваивания учтёт это. Только лямбда (будучи анонимным определением функции) считается для этих целей отдельной областью видимости.

Существует один особый случай: выражение присваивания, встречающееся в генераторах списков, множеств, словарей или же в самих «выражениях генераторах» (ниже все вместе именуемые «генераторами» (comprehensions) ), привязывает переменную к области видимости, которая содержит генератор, соблюдая модификатор globab или nonglobal, если таковой существует.

Обоснование для этого особого случая двояко. Во-первых, это позволяет нам удобно захватывать «участника» в выражениях any () и all(), например:

if any((comment := line).startswith('#') for line in lines):
    print("First comment:", comment)
else:
    print("There are no comments")

if all((nonblank := line).strip() == '' for line in lines):
    print("All lines are blank")
else:
    print("First non-blank line:", nonblank)

Во-вторых, это предоставляет компактный способ обновления переменной из генератора, например:

# Compute partial sums in a list comprehension
total = 0
partial_sums = [total := total + v for v in values]
print("Total:", total)

Однако имя переменной из выражения присваивания не может совпадать с именем, которое уже используется в генераторах циклом for для итерации. Последние имена являются локальными по отношению к генератору, в котором появляются. Было бы противоречиво, если бы выражения присваивания ссылались ещё и к области видимости внутри генератора.

Например, [i: = i + 1 for i in range(5)] недопустимо: цикл for устанавливает, что i является локальной для генератора, но часть «i := i+1» настаивает на том, что i является переменной из внешней области видимости. По той же причине следующие примеры не сработают:


[[(j := j) for i in range(5)] for j in range(5)] # НЕДОПУСТИМО
[i := 0 for i, j in stuff]                       # НЕДОПУСТИМО
[i+1 for i in (i := stuff)]                      # НЕДОПУСТИМО

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

[False and (i := 0) for i, j in stuff]     # НЕДОПУСТИМО
[i for i, j in stuff if True or (j := 1)]  # НЕДОПУСТИМО

# [прим. для новичков. Из-за "ленивой" реализации логических 
# операторов, второе условие никогда не вычислится в обоих
# случаях, ведь результат заранее известен, но ошибка будет]

Для тела генератора (часть перед первым ключевым словом «for») и выражения-фильтра (часть после «if» и перед любым вложенным «for») это ограничение применяется исключительно к именам перемененных, которые одновременно используются в качестве итерационных переменных. Как мы уже сказали, Лямбда-выражения вводят новую явную область видимости функции и следовательно могут использоваться в выражениях генераторов без дополнительных ограничений. [прим. опять же, кроме таких случаев: [i for i in range(2, (lambda: (s:=2)() ))] ]

Из-за конструктивных ограничений в эталонной реализации (анализатор таблицы символов не может распознать, используются ли имена из левой части генератора в оставшейся части, где находится итерируемое выражение), поэтому выражения присваивания полностью запрещены как часть итерируемых (в части после каждого «in» и перед любым последующим ключевым словом «if» или «for»). То есть все эти случаи недопустимы:

[i+1 for i in (j := stuff)]                    # НЕДОПУСТИМО
[i+1 for i in range(2) for j in (k := stuff)]  # НЕДОПУСТИМО
[i+1 for i in [j for j in (k := stuff)]]       # НЕДОПУСТИМО
[i+1 for i in (lambda: (j := stuff))()]        # НЕДОПУСТИМО

Еще одно исключение возникает, когда выражение присваивания применяется в генераторах, которые находятся в области видимости класса. Если при использовании выше перечисленных правил должно произойти создание перемеренной в области видимости класса, то такое выражение присваивания недопустимо и приведёт к возникновению SyntaxError:

class Example:
    [(j := i) for i in range(5)]  # НЕДОПУСТИМО

(Причиной последнего исключения является неявная область видимости функции, созданной генератором — в настоящее время нет runtime механизма для функций, чтобы сослаться на переменную, расположенную в области видимости класса, и мы не хотим добавлять такой механизм. Если эта проблема когда-либо будет решена, то этот особый случай (возможно) будет удалён из спецификации выражений присваивания. Обратите внимание, что эта проблема возникнет, даже если вы раннее создали переменную в области видимости класса и пытаетесь изменить её выражением присваивания из генератора.)

Смотрите приложение B для примеров того, как выражения присваивания находящиеся в генераторах, преобразуются в эквивалентный код.

Относительный приоритет :=

Оператор := группируется сильнее, чем запятая во всех синтаксических позициях где это возможно, но слабее, чем все другие операторы, включая or, and, not, и условные выражения (A if C else B). Как следует из раздела «Исключительные случаи» выше, выражения присваивания никогда не работают на том же «уровне», что и классическое присваивание =. Если требуется другой порядок операций, используйте круглые скобки.

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

x := 0 # ЗАПРЕЩЕНО

(x := 0) # Рабочая альтернатива

x = y := 0 # ЗАПРЕЩЕНО

x = (y := 0) # Рабочая альтернатива

len(lines := f.readlines()) # Разрешено

foo(x := 3, cat='vector') # Разрешено

foo(cat=category := 'vector') # ЗАПРЕЩЕНО

foo(cat=(category := 'vector')) # Рабочая альтернатива

Большинство приведенных выше «допустимых» примеров не рекомендуется использовать на практике, поскольку люди, быстро просматривающие ваш исходный код, могут не правильно понять его смысл. Но в простых случаях это разрешено:

# Valid
if any(len(longline := line) >= 100 for line in lines):
    print("Extremely long line:", longline)

Этот PEP рекомендует абсолютно всегда ставить пробелы вокруг :=, аналогично рекомендации PEP 8 для = для классического присваивания. (Отличие последней рекомендации в том, что она запрещает пробелы вокруг =, который используется для передачи ключевых аргументов функции.)

Изменение порядка вычислений.

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

  • В генераторах словарей {X: Y for …}, Y в настоящее время вычисляется перед X. Мы предлагаем изменить это так, чтобы X вычислялся до Y. (В классическом dict, таком как {X: Y}, а также в dict((X, Y) for …) это уже реализовано. Поэтому и генераторы словарей должны соответствовать этому механизму)

Различия между выражениями присваивания и инструкциями присваивания.

Что наиболее важно, «:=» является выражением, а значит его можно использовать в случаях, когда инструкции недопустимы, включая лямбда-функции и генераторы. И наоборот, выражения присваивания не поддерживают расширенный функционал, который можно использовать в инструкциях присваивания:

  • Каскадное присваивание не поддерживается на прямую
    x = y = z = 0  # Equivalent: (z := (y := (x := 0)))
  • Отдельные «цели», кроме простого имени переменной NAME, не поддерживаются:
    # No equivalent
    a[i] = x
    self.rest = []
  • Функционал и приоритет «вокруг» запятых отличается:
    x = 1, 2  # Sets x to (1, 2)
    (x := 1, 2)  # Sets x to 1
  • Распаковка и упаковка значений не имеют «чистую» эквивалентность или вообще не поддерживаются
    # 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)

Спецификация изменяется во время реализации

Следующие изменения были сделаны на основе полученного опыта и дополнительного анализа после первого написания данного PEP и перед выпуском Python 3.8:

  • Для обеспечения согласованности с другими подобными исключениями, а также чтобы не вводить новое название, которое не обязательно будет удобно для конечных пользователей, первоначально предложенный подкласс TargetScopeError для SyntaxError был убран и понижен до обычного SyntaxError. [3]
  • Из-за ограничений в анализе таблицы символов CPython, эталонная реализация выражения присваивания вызывает SyntaxError для всех случаев использования внутри итераторов. Раньше это исключение возникало только если имя создаваемой переменной совпадало с тем, которое уже используется в итерационном выражении. Это может быть пересмотрено при наличии достаточно убедительных примеров, но дополнительная сложность кажется нецелесообразной для чисто «гипотетических» вариантов использования.

Примеры

Примеры из стандартной библиотеки Python

site.py

env_base используется только в условии, поэтому присваивание можно поместить в if, как «заголовок» логического блока.

  • Текущий код:
    env_base = os.environ.get("PYTHONUSERBASE", None)
    if env_base:
        return env_base
  • Улучшенный код:
    if env_base := os.environ.get("PYTHONUSERBASE", None):
        return env_base

_pydecimal.py

Вы можете избегать вложенных if, тем самым удалив один уровень отступов.

  • Текущий код:
    if self._is_special:
        ans = self._check_nans(context=context)
        if ans:
            return ans
  • Улучшенный код:
    if self._is_special and (ans := self._check_nans(context=context)):
        return ans

copy.py

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

  • Текущий код:
    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(deep)copyable object of type %s" % cls)
  • Улучшенный код:
    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(deep)copyable object of type %s" % cls)

datetime.py

tz используется только для s += tz. Перемещение его внутрь if помогает показать его логическую область использования.

  • Текущий код:
    s = _format_time(self._hour, self._minute,
                     self._second, self._microsecond,
                     timespec)
    tz = self._tzstr()
    if tz:
        s += tz
    return s
  • Улучшенный код:
    s = _format_time(self._hour, self._minute,
                     self._second, self._microsecond,
                     timespec)
    if tz := self._tzstr():
        s += tz
    return s

sysconfig.py

Вызов fp.readline(), как «условие» в цикле while ( а также вызов метода .match() ) в условии if делает код более компактным, не усложняя его понимание.

  • Текущий код:
    while True:
        line = fp.readline()
        if not line:
            break
        m = define_rx.match(line)
        if m:
            n, v = m.group(1, 2)
            try:
                v = int(v)
            except ValueError:
                pass
            vars[n] = v
        else:
            m = undef_rx.match(line)
            if m:
                vars[m.group(1)] = 0
  • Улучшенный код:
    while line := fp.readline():
        if m := define_rx.match(line):
            n, v = m.group(1, 2)
            try:
                v = int(v)
            except ValueError:
                pass
            vars[n] = v
        elif m := undef_rx.match(line):
            vars[m.group(1)] = 0

Упрощение генераторов списков

Теперь генератор списка может эффективно фильтроваться путем «захвата» условия:

results = [(x, y, x/y) for x in input_data if (y := f(x)) > 0]

После этого переменная может быть повторно использована в другом выражении:

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

Ещё раз обратите внимание, что в обоих случаях переменная y находится в той же области видимости, что и переменные result и stuff.

«Захват» значений в условиях

Выражения присваивания могут быть эффективно использованы в условиях оператора if или while:

# Loop-and-a-half
while (command := input("> ")) != "quit":
    print("You entered:", command)

# Capturing regular expression match objects
# See, for instance, Lib/pydoc.py, which uses a multiline spelling
# of this effect
if match := re.search(pat, text):
    print("Found:", match.group(0))
# The same syntax chains nicely into 'elif' statements, unlike the
# equivalent using assignment statements.
elif match := re.search(otherpat, text):
    print("Alternate found:", match.group(0))
elif match := re.search(third, text):
    print("Fallback found:", match.group(0))

# Reading socket data until an empty string is returned
while data := sock.recv(8192):
    print("Received data:", data)

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

Fork

Пример из низкоуровневого мира UNIX: [прим. Fork() — системный вызов в Unix-подобных операционных системах, создающий новый под-процесс, по отношению к родительскому.]

if pid := os.fork():
    # Parent code
else:
    # Child code

Отклоненные альтернативны

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

Изменение области видимости для генераторов

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

Альтернативные варианты написания

В целом, предложенные выражения присваивания имеют ту же семантику, но пишутся по-другому.

  1. EXPR as NAME:
    stuff = [[f(x) as y, x/y] for x in range(5)]

    Так как конструкция EXPR as NAME уже имеет семантический смысл в выражениях import, except и with, это могло создать ненужную путаницу и некоторые ограничения (например, запрет выражения присваивания внутри заголовков этих конструкций).

    (Обратите внимание, что «with EXPR as VAR» не просто присваивает значение EXPR в VAR, а вызывает EXPR.__enter__() и уже после присваивает полученный результат в VAR.)

    Дополнительные причины, чтобы предпочесть «:=» выше предложенному написанию:

    • В том случае, если if f(x) as y не бросится вам в глаза, то его можно ​​случайно прочитать как if f x blah-blah, и визуально такая конструкция слишком похожа на if f(x) and y.
    • Во всех других ситуациях, когда as разрешено, даже читателям со средними навыками приходится прочитывать всю конструкцию от начала, чтобы посмотреть на ключевое слово:
      • import foo as bar
      • except Exc as var
      • with ctxmgr() as var

      И наоборот, as не относится к оператором if или while и мы преднамеренно создаём путаницу, допуская использование as в «не родной» для него среде.

    • Также существует «параллель» соответствия между
      • NAME = EXPR
      • if NAME := EXPR

      Это усиливает визуальное распознавание выражений присваивания.

  2. EXPR -> NAME
    stuff = [[f(x) -> y, x/y] for x in range(5)]

    Этот синтаксис основан на таких языках, как R и Haskell, ну и некоторых программируемых калькуляторах. (Обратите внимание, что направление стрелки справа-налево y < — f (x) невозможно в Python, поскольку конструкция будет интерпретироваться как меньше-чем и унарный минус.) Данный синтаксис имеет небольшое преимущество перед «as» в том смысле, что не конфликтует с конструкциями import, except и with, но в остальном проблемы те же. Но эти проблемы совершенно не связано с другим использованием такой стрелки в Python (в аннотациях возвращаемого типа функции), а просто по сравнению с «:=» (которое восходит к Algol-58) стрелочки менее привычны для присваивания.

  3. Добавление оператора «точка» к именам локальных переменных
    stuff = [[(f(x) as .y), x/.y] for x in range(5)] # with "as"
    stuff = [[(.y := f(x)), x/.y] for x in range(5)] # with ":="

    Это позволит легко обнаруживать и устраняя некоторые формы синтаксической неоднозначности. Однако такое нововведение стало бы единственным местом в Python, где область видимости переменной закодирована в ее имени, что затрудняет рефакторинг.

  4. Добавление where: к любой инструкции для создания локальных имен:
    value = x**2 + 2*x where:
        x = spam(1, 4, 7, q)

    Порядок выполнения инвертирован (часть с отступом выполнится первой, а затем последует срабатывание «заголовка»). Это потребует введения нового ключевого слова, хотя возможно «перепрофилирование» другого (скорее всего with:). См. PEP 3150, где раннее обсуждался этот вопрос (предложенным там словом являлось given: ).

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

    Этот синтаксис меньше конфликтует с другими, чем as (если только не считать конструкции raise Exc from Exc), но в остальном сравним с ними. Вместо параллели с with expr as target: (что может быть полезно, но может и сбить с толку), этот вариант вообще не имеет параллелей ни с чем, но к удивлению лучше запоминается.

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

Один из самых популярных вариантов использования выражений присваивания — это операторы if и while. Вместо более общего решения, использование as улучшает синтаксис этих двух операторов, добавляя средство захвата сравниваемого значения:

if re.search(pat, text) as match:
    print("Found:", match.group(0))

Это прекрасно работает, но ТОЛЬКО, когда желаемое условие основано на «правильности» возвращаемого значения. Таким образом, данный способ эффективен для конкретных случаев (проверки совпадения регулярных выражений, чтения сокетов, возвращающее пустую строку, когда заканчивается выполнение), и совершенно бесполезен в более сложных случаях (например, когда условие равно f(x) < 0, и вы хотите сохранить значение f(x) ). Также это не имеет смысла в генераторах списков.

Преимущества: нет синтаксических неясностей. Недостатки: даже если пользоваться им только в операторах if/while, хорошо работает лишь в части случаев.

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

Другим распространенным вариантом использования выражения присваивания являются генераторы (list/set/dict и genexps). Как и выше, были сделаны предложения для конкретных решений.

  1. where, let, or given:
    stuff = [(y, x/y) where y = f(x) for x in range(5)]
    stuff = [(y, x/y) let y = f(x) for x in range(5)]
    stuff = [(y, x/y) given y = f(x) for x in range(5)]

    Этот способ приводит появлению подвыражения между циклом «for» и основным выражением. Он также вводит дополнительное ключевое слово языка, что может создать конфликты. Из трех вариантов, where является наиболее чистым и читабельным, но потенциальные конфликты всё ещё существуют (например, SQLAlchemy и numpy имеют свои методы where, также как и tkinter.dnd.Icon в стандартной библиотеке).

  2. with NAME = EXPR:
    stuff = [(y, x/y) with y = f(x) for x in range(5)]

    Всё тоже самое, как и в верхнем пункте, но используется ключевое слово with. Неплохо читается и не нуждается в дополнительном ключевом слове. Тем не менее, способ более ограничен и не может быть легко преобразован в «петлевой» цикл for. Имеет проблему языка C, где знак равенства в выражении теперь может создавать переменную, а не выполнять сравнение. Также возникает вопрос: «А почему «with NAME = EXPR:» не может быть использовано просто как выражение, само по себе?»

  3. with EXPR as NAME:
    stuff = [(y, x/y) with f(x) as y for x in range(5)]

    Похоже на второй вариант, но с использованием as, а не знака равенства. Синтаксически родственно другими видами присваивания промежуточных имён, но имеет те же проблемы с циклами for. Смысл при использованием ключевого слова with в генераторах и в качестве отдельной инструкции будет совершенно различным

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

Понижение приоритета оператора

Оператор := имеет два логических приоритета. Либо он должен иметь настолько низкий приоритет, насколько это возможно (наравне оператора присваивания). Либо должен иметь приоритет больший, чем операторы сравнения. Размещение его приоритета между операторами сравнения и арифметическими операциями (если быть точным: чуть ниже, чем побитовое ИЛИ) позволит при использовании операторов while и if в большинстве случаев обходиться без скобок, так как более вероятно, что вы хотите сохранить значение чего-либо до того, как выполнится сравнение над ним:

pos = -1
while pos := buffer.find(search_term, pos + 1) >= 0:
    ...

Как только find() возвращает -1, цикл завершается. Если := связывает операнды также свободно, как и =, то результат find() будет сначала «захвачен» в оператор сравнения и вернёт обычно значение True, либо False, которое менее полезно.

Хоть такое поведение и было бы удобно на практике во многих ситуациях, но его и было бы сложнее объяснить. А так мы можем сказать, что «оператор := ведет себя так же, как и оператор обычного присваивания». То есть приоритет для := был выбран максимально близко к оператору = (за исключением того, что := имеет приоритет выше, чем запятая).

Даёшь запятые справа

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

(point := (x, y))
(point := x, y)

(В текущей версии стандарта последняя запись будет эквивалентна выражению ((point: = x), y) .)

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

foo (x: = 1, y)
foo (x: = (1, y))

И мы получаем единственный менее запутанный выход: сделать оператор := меньшего приоритета, чем запятую.

Всегда требующие скобки

Было предложено всегда заключать в скобки выражения присваивания. Это избавило бы нас от многих двусмысленностей. И действительно, скобки часто будут необходимы, чтобы извлечь желаемое значение. Но в следующих случаях наличие скобок явно показалось нам излишними:

# Top level in if
if match := pattern.match(line):
    return match.group(1)

# Short call
len(lines := f.readlines())

Частые возражения

Почему бы просто не превратить инструкции присваивания в выражения?

C и подобные ему языки определяют оператор = как выражение, а не инструкцию, как это делает Python. Это позволяет осуществлять присваивание во многих ситуациях, включая места, где происходит сравнение переменных. Синтаксическое сходство между if (x == y) и if (x = y) противоречит их резко отличающейся семантике. Таким образом, этот PEP вводит оператор := для уточнения их различия.

Зачем заморачиваться с выражениями присваивания, если существуют инструкции присваивания?

Две этих формы имеют различные гибкие возможности. Оператор := можно использовать внутри большего выражения, а в операторе = может использоваться «семейством мини-операторов» по типу «+=». Также = позволяет присваивать значения по атрибутам и индексам.

Почему бы не использовать локальную область видимости и предотвратить загрязнение пространства имен?

Предыдущие версии этого стандарта включали в себя реальную локальную область действия (ограниченную одним оператором) для выражений присваивания, предотвращая утечку имен и загрязнения пространства имен. Несмотря на то, что в ряде ситуаций это давало определенное преимущество, во многих других это усложняет задачу, и выгоды не оправдываются преимуществами существующего подхода. Это сделано в интересах простоты языка. Вам больше не нужна эта переменная? Есть выход: удалите переменную через ключевое слово del или добавьте к её названию нижнее подчеркивание.

(Автор хотел бы поблагодарить Гвидо ван Россума и Кристофа Грота за их предложения по продвижению стандарта PEP в этом направлении. [2])

Рекомендации по стилю

Поскольку выражения присваивания иногда могут использоваться наравне с оператором присваивания, возникает вопрос, чему всё-таки отдавать предпочтение?.. В соответствии с другими соглашениями о стиле (такими, как PEP 8), существует две рекомендации:

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

Благодарность

Авторы этого стандарта хотели бы поблагодарить Ника Коглана (Nick Coghlan) и Стивена Д’Апрано (Steven D’Aprano) за их значительный вклад в этот PEP, а также членов Python Core Mentorship за помощь в реализации.

Приложение A: выводы Тима Петерса

Вот краткое эссе, которое Тим Питерс написал на данную тематику.

Мне не нравятся «замороченный» код, а также не нравится помещать концептуально не связанную логику в одну строку. Так, например, вместо:

i = j = count = nerrors = 0

Я предпочитаю писать:

i = j = 0
count = 0
nerrors = 0

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

mylast = mylast[1]
yield mylast[0]

Значительно лучше, чем это:

yield (mylast := mylast[1])[0]

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

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

Но такие случаи редки. Задача сохранения результата встречается очень часто, и «разреженное лучше, чем плотное» не означает, что «почти пустое лучше, чем разреженное» [прим. отсылка к Дзену пайтона]. Например, у меня есть много функций, которые возвращают None или 0, чтобы сообщить «У меня нет ничего полезного, но так как это часто происходит, я не хочу надоедать вам исключениями». По сути, этот механизм используется и в регулярных выражениях, которые возвращают None, когда нет совпадений. Поэтому в таком примере много кода:

result = solution(xs, n)
if result:
    # use result

Я считаю следующий вариант более понятным, и конечно же более удобным для чтения:

if result := solution(xs, n):
    # use result

Сначала я не придавал этому особого значения, но такая короткая конструкция появлялась настолько часто, что меня довольно скоро начало раздражать, что я не могу воспользоваться ею. Это меня удивило! [прим. видимо это было написано до того, как официально вышел Python 3.8]

Есть и другие случаи, когда выражения присваивания действительно «выстреливают». Вместо того, чтобы ещё раз порыться в моём коде, Кирилл Балунов (Kirill Balunov) привел прекрасный пример функции copy() из стандартной библиотеки copy.py:

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 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)

Простое использование выражений присваивания позволяет визуальной структуре кода подчеркнуть «плоскость» логики. А вот постоянно увеличивающийся отступ делает её неявной.

Вот ещё небольшой пример из моего кода, который очень порадовал меня, поскольку позволял поместить внутренне связанную логику в одну строку и удалить раздражающий «искусственный» уровень отступа. Это именно то, чего я хочу от оператора if и это позволяет упростить чтение. Следующий код:

diff = x - x_base
if diff:
    g = gcd(diff, n)
    if g > 1:
        return g

Превратился в:

if (diff := x - x_base) and (g := gcd(diff, n)) > 1:
    return g

Итак, в большинстве строк, где происходит присваивание переменной, я бы не использовал выражения присваивания. Но эта конструкция настолько частая, что всё ещё есть много мест, где я бы воспользовался такой возможностью. В большинстве последних случаев я немного выиграл, поскольку они часто появлялись. В оставшейся под-части это привело к средним или большим улучшениям. Таким образом, я бы использовал выражения присваивания гораздо чаще, чем тройной if, но и значительно реже, чем augmented assignment [прим. короткие варианты: *=, /=, += и т.д.].

Числовой пример

У меня есть еще один пример, который поразил меня раннее.

Если все переменные являются положительными целыми числами, а переменная a больше n-ого корня из x, то этот алгоритм возвращает «нижнее» округление n-го корня из x (и примерно удваивает количество точных битов за итерацию):

while a > (d := x // a**(n-1)):
    a = ((n-1)*a + d) // n
return a

Непонятно почему, но такой вариант алгоритма менее очевиден, нежели бесконечный цикл с условной веткой break (loop and a half). Также трудно доказать правильность этой реализации, не опираясь на математическое утверждение («среднее арифметическое — среднее геометрическое неравенство») и не зная некоторых нетривиальных вещей о том, как ведут себя вложенные функции округления в меньшую сторону. Но здесь уже проблема заключена в математике, а не в программировании.

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

while True:
    d = x // a**(n-1)
    if a <= d:
        break
    a = ((n-1)*a + d) // n
return a

Приложение B: Грубый интерпретатор кода для генераторов

В этом приложении делается попытка прояснить (хотя и не указать) правила, по которым должно происходить создание переменной в генераторных выражениях. Для ряда иллюстративных примеров мы покажем исходный код, где генератор заменяется эквивалентной ему функцией в комбинации с некоторыми «строительными лесами».

Поскольку [x for …] эквивалентно list(x for …), то примеры не теряют своей общности. И поскольку эти примеры предназначены лишь для разъяснения общих правил, они не претендуют на реалистичность.

Примечание: генераторы сейчас реализованы через создание вложенных функций-генераторов (подобных тем, которые приведены в этом приложении). В примерах показана новая часть, которая добавляет соответствующий функционал для работы с областью видимости выражений присваивания (такой областью видимости, как если бы присваивание было выполнено в блоке, содержащем самый внешний генератор). Для упрощения «вывода типов» (type inference), эти иллюстративные примеры не учитывают, что выражения присваивания являются необязательными (но они учитывают, в какой область видимости будет находиться переменная, созданная внутри генератора).

Давайте сначала вспомним, какой код создаётся «под капотом» для генераторов без выражений присваивания:

  • Исходный код (EXPR чаще всего использует в себе переменную VAR):
    def f():
        a = [EXPR for VAR in ITERABLE]
  • Преобразованный код (давайте не будем беспокоиться о конфликтах имен):
    def f():
        def genexpr(iterator):
            for VAR in iterator:
                yield EXPR
        a = list(genexpr(iter(ITERABLE)))

Давайте добавим простое выражение присваивания.

  • Исходный код:
    def f():
        a = [TARGET := EXPR for VAR in ITERABLE]
    
  • Преобразованный код:
    def f():
        if False:
            TARGET = None  # Dead code to ensure TARGET is a local variable
        def genexpr(iterator):
            nonlocal TARGET
            for VAR in iterator:
                TARGET = EXPR
                yield TARGET
        a = list(genexpr(iter(ITERABLE)))

Теперь давайте добавим инструкцию global TARGET в объявление функции f().

  • Исходный код:
    def f():
        global TARGET
        a = [TARGET := EXPR for VAR in ITERABLE]
    
  • Преобразованный код:
    def f():
        global TARGET
        def genexpr(iterator):
            global TARGET
            for VAR in iterator:
                TARGET = EXPR
                yield TARGET
        a = list(genexpr(iter(ITERABLE)))

Или наоборот, давайте добавим nonlocal TARGET в объявление функции f().

  • Исходный код:
    def g():
        TARGET = ...
        def f():
            nonlocal TARGET
            a = [TARGET := EXPR for VAR in ITERABLE]
    
  • Преобразованный код:
    def g():
        TARGET = ...
        def f():
            nonlocal TARGET
            def genexpr(iterator):
                nonlocal TARGET
                for VAR in iterator:
                    TARGET = EXPR
                    yield TARGET
            a = list(genexpr(iter(ITERABLE)))

И наконец, давайте вложим два генератора.

  • Исходный код:
    def f():
        a = [[TARGET := i for i in range(3)] for j in range(2)]
        # I.e., a = [[0, 1, 2], [0, 1, 2]]
        print(TARGET)  # prints 2
    
  • Преобразованный код:
    def f():
        if False:
            TARGET = None
        def outer_genexpr(outer_iterator):
            nonlocal TARGET
            def inner_generator(inner_iterator):
                nonlocal TARGET
                for i in inner_iterator:
                    TARGET = i
                    yield i
            for j in outer_iterator:
                yield list(inner_generator(range(3)))
        a = list(outer_genexpr(range(2)))
        print(TARGET)

Приложение C: Никаких изменений в семантике области видимости

Обратите внимание, что в Python семантика области видимости не изменилась. Области видимости локальных функций по-прежнему определяются во время компиляции и имеют неопределенную временную протяженность во время выполнения (замыкания). Пример:

a = 42
def f():
    # `a` is local to `f`, but remains unbound
    # until the caller executes this genexp:
    yield ((a := i) for i in range(3))
    yield lambda: a + 100
    print("done")
    try:
        print(f"`a` is bound to {a}")
        assert False
    except UnboundLocalError:
        print("`a` is not yet bound")

Тогда:

>>> results = list(f()) # [genexp, lambda]
done
`a` is not yet bound
# The execution frame for f no longer exists in CPython,
# but f's locals live so long as they can still be referenced.
>>> list(map(type, results))
[<class 'generator'>, <class 'function'>]
>>> list(results[0])
[0, 1, 2]
>>> results[1]()
102
>>> a
42

Ссылки

  1. Доказательство реализации концепции
  2. Обсуждение семантики выражений присваивания (с VPN туго, но грузится)
  3. Обсуждение TargetScopeError в PEP 572 (грузится аналогично предыдущему)

Авторские права

Этот документ был размещен в открытом доступе.

Источник: github.com/python/peps/blob/master/pep-0572.rst

Моя часть

Для начала, подведём итоги:

  • Чтобы люди не говнокодили убрать смысловую двойственность, во многих «классических» местах, где можно было бы использовать и «=» и «:=» есть ограничения, поэтому оператор «:=» нужно часто заключать в скобки. Эти случаи придётся просмотреть в разделе, описывающем базовое использование.
  • Приоритет выражений присваивания чуть выше, чем у запятой. Благодаря этому, при присваивании не образуются кортежи. Также это даёт возможность использовать оператор := при передаче аргументов в функцию.
  • Выражения присваивания, находящиеся в генераторах, используют ту область видимости, в которой находится генератор. Это позволяет сохранять значения для повторного использования. А вот в lambda функциях это не сработает, они создают свою «анонимную» область видимости.
  • Теперь и в генераторах словарей строго определён порядок вычислений: сначала считается ключ, а потом соответствующее ему значение
  • Нельзя изменить в генераторе через присваивание переменную, использующуюся в итераторе.
  • Можно отстрелить левую ногу при попытке через генератор с присваиванием изменить/создать переменную класса.
  • Можно отстрелить правую ногу, подставив выражение присваивания в итерационное выражение.

В итоге, я хочу сказать, что мне понравился новый оператор. Он позволяет писать более плоский код в условиях, «фильтровать» списки, а также (наконец-то) убрать «ту самую», одинокую строчку перед if. Если люди будут использовать выражения присваивания по назначению, то это будет очень удобный инструмент, который повысит читабельность и красоту кода (Хотя, такое можно сказать про любой функционал языка….)

Python – невероятно функциональный и простой в изучении язык программирования, который имеет свои отличительные особенности по сравнению с другими:

  1. Кроссплатформенность. Поскольку Python – это интерпретируемый язык программирования, для многих платформ можно воспользоваться его интерпретатором. Поэтому написанные с его помощью приложения могут использоваться на самых разных устройствах.
  2. Для Python доступно большое количество средств разработки, фреймворков и сервисов. Поэтому не составит труда найти тот вариант, который подходит именно вам.
  3. Возможность подключать библиотеки, которые написаны на C. Это позволяет увеличить эффективность и быстродействие приложений, которые создаются с использованием этого языка программирования.

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

Поэтому сегодня мы более подробно поговорим о том, как правильно использовать инструкции и выражения Python. Также разберемся, в чем выражаются различия между ними. Помимо этого, мы поговорим об инструкциях, которые занимают несколько строк и об отступах в Python. Также попробуем дать ответ на вопрос, зачем отступы вообще нужны в этом языке программирования. Ведь в других же они не используются настолько часто!

Содержание

  1. Инструкции в Python
  2. Понятие инструкции
  3. Что такое выражение?
  4. Простая операция присваивания
  5. Пример 1: с правой стороны – выражение со значением
  6. Пример 2. С правой стороны – существующая переменная Python
  7. Пример 3. С правой стороны – операция.
  8. Дополнительная инструкция присваивания
  9. Инструкция в несколько строк
  10. 1. Явное продолжение строки
  11. 2. Неявное продолжение строки
  12. Реализация отступов в Python
  13. Сколько места занимает отступ?
  14. Почему без отступов в Python не получится обойтись?
  15. Выводы

Инструкции в Python

Понятие инструкции

Под инструкцией подразумевается логическое выражение, доступное для прочтения и выполнения интерпретатором Python. Он может являться выражением либо оператором присваивания в этом языке программирования.

Присваивание используется в языке повсеместно. С его помощью мы задаем метод создания и хранения объектов с использованием выражений.

Что такое выражение?

Выражение — это тип оператора, который содержит логическую последовательность чисел, строк, объектов и операторов Python. Значение и переменная сами по себе являются выражениями.

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

# Использование арифметических выражений

>>> ((20 + 2) * 10 / 5 - 20)

24.0




# Использование функций в выражении

>>> pow(2, 10)

1024




# Использование eval в выражении

>>> eval("2.5+2.5")

5.0

Простая операция присваивания

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

# Синтаксис

variable = expression

Давайте теперь попробуем рассмотреть стандартные типы выражений присваивания в Python и посмотрим, как устроена их работа.

Пример 1: с правой стороны – выражение со значением

Это стандартная форма присваивания в этом языке программирования. 

>>> test = "Изучение python"

В этом примере Python создает строку «Изучение Python» в памяти, после чего присваивает ей имя test. Чтобы узнать, по какому адресу в памяти она расположена, необходимо воспользоваться встроенной функцией id()

>>> test = "Изучение python"

>>> id(test)

6589040

Номер — это адрес места, где хранится значение. Вот несколько интересных моментов, о которых следует помнить.

  1. Если вы создадите другую строку с тем же значением, Python создаст новый объект и назначит его другому месту в памяти. Это работает в большинстве случаев.
  2. Однако в следующих двух случаях он использует одну и ту же ячейку памяти:
    • Строки без пробелов, содержащие менее 20 символов;
    • Целые числа от -5 до 255.

Это называется интернированием и делается для сохранения памяти.

Пример 2. С правой стороны – существующая переменная Python

Теперь давайте приведем еще один пример, как может использоваться инструкция присваивания. С правой стороны расположена переменная Python, которая была объявлена ранее.  

Если мы используем инструкцию, приведенную выше, у нас не будет выделено дополнительное пространство в памяти. Обе переменных будут ссылаться на одинаковй объект в памяти. Это аналогично созданию псевдонима для того объекта, который уже существует. Чтобы в этом удостовериться, можно воспользоваться той же функцией id()

>>> test = "Изучение python"

>>> id(test)

6589424

>>> another_test = test

>>> id(another_test)

6589424

Пример 3. С правой стороны – операция.

А если мы используем такую инструкцию, то исход операции непосредственно влияет на ее результат. Давайте приведем такой пример для наглядности. 

>>> test = 2 * 2 / 4

>>> print(test)

1.0

>>> type(test)

В этом примере присваивание приведет к созданию переменной типа float. А в этом – к появлению переменной типа int

>>> test = 3 * 3

>>> print(test)

9

>>> type(test)

Дополнительная инструкция присваивания

Арифметические операторы можно комбинировать для формирования инструкций дополненного присваивания.

Рассмотрим этот пример: x + = y. Это похоже на эту инструкцию — x = x + y.

Следующий пример, добавляющий новые элементы в кортеж, немного более наглядно демонстрирует принцип. 

>>> my_tuple = (5, 10, 20)

>>> my_tuple += (40, 80,)

>>> print(my_tuple)

(5, 10, 20, 40, 80)

А этот пример – это список, элементами которого являются гласные буквы английского алфавита. В нем в список добавляются недостающие значения. 

>>> list_vowels = ['a','e','i']

>>> list_vowels += ['o', 'u',]

>>> print(list_vowels)

['a', 'e', 'i', 'o', 'u']

Инструкция в несколько строк

Каждая инструкция Python оканчивается знаком новой строки. Но это действие можно расширить до нескольких строк, используя символ .

В Python есть два способа обработки операторов, занимающих несколько строк.

1. Явное продолжение строки

Когда символ продолжения строки сразу используется для разделения инструкции на несколько строк. 

# Инициализация списка с помощью многострочной инструкции

>>> my_list = [1, 

... 2, 3

... ,4,5 

... ]

>>> print(my_list)

[1, 2, 3, 4, 5]

# Вычислить выражение, используя многострочную инструкцию

>>> eval ( 

... " 2.5 

... + 

... 3.5")

6.0

2. Неявное продолжение строки

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

>>> result = (10 + 100

... * 5 - 5

... / 100 + 10

... )

>>> print(result)

519.95

 

>>> subjects = [

... 'Математика',

... 'Английский',

... 'Химия'

... ]

>>> print(subjects)

['Математика', 'Английский', 'Химия']

>>> type(subjects)

Реализация отступов в Python

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

Тот блок кода, который играет роль тела функции либо цикла, начинается с отступа и завершается первой строкой, в которой отступа нет.

Сколько места занимает отступ?

Общепринятый размер отступа в Python – 4 символа. Правда, Google предусматривает немного другие правила: размер может ограничиваться исключительно двумя символами. Таким образом, можно руководствоваться и собственным стилем, задавая такое количество отступов, которое нужно. Тем не менее, рекомендуется все же делать четыре отступа.

Почему без отступов в Python не получится обойтись?

Большинство языков программирования используют отступы для улучшения читабельности, но добавлять отступы необязательно. Но в Python их обязательно надо применять, в этом особенность данного языка программирования. Обычно каждой строке предшествует 4-символьный отступ для блока кода.

В примерах из предыдущих разделов были блоки без отступов. Однако в более сложных выражениях без них не обойтись. 

def demo_routine(num):

    print('Демо функция')

    if num % 2 == 0:

        return True

    else:

        return False




num = int(input('Введи число:'))

if demo_routine(num) is True:

    print(num, 'четное число')

else:

    print(num, 'нечетное число')

А также давайте рассмотрим пример случая, когда ненужный отступ приводит к возникновению ошибки. 

>>>  6*5-10

File "", line 1

    6*5-10

    ^

IndentationError: unexpected indent

Выводы

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

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

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

Оцените качество статьи. Нам важно ваше мнение:

Привет, Хабр. В этот раз мы рассмотрим PEP 572, который рассказывает про выражения присваивания. Если Вы до сих пор скептически относитесь к оператору «:=» или не до конца понимаете правила его использования, то эта статья для Вас. Здесь вы найдёте множество примеров и ответов на вопрос: «Почему именно так?». Эта статья получилась максимально полной и если у Вас мало времени, то просмотрите раздел, написанный мной. В его начале собраны основные «тезисы» для комфортной работы с выражениями присваивания.

PEP 572 — Выражения Присваивания

Содержание

  • Аннотация
  • Обоснование
  • Синтаксис и семантика
  • Спецификация изменяется во время реализации
  • Примеры
  • Отклоненные альтернативны
  • Частые возражения
  • Рекомендации по стилю
  • Благодарность
  • Приложение A: выводы Тима Петерса
  • Приложение B: Грубый интерпретатор кода для генераторов
  • Приложение C: Никаких изменений в семантике области видимости
  • Ссылки
  • Авторские права
  • Моя часть

Аннотация

Это соглашение расскажет о появившейся возможности присваивания внутри выражений, с помощью нового обозначения NAME := expr.

В рамках нововведений был обновлен порядок вычисления генераторов словарей (dictionary comprehension). Это гарантирует, что выражение ключа вычислится перед выражением значения (это позволяет привязывать ключ к переменной, а затем повторно использовать созданную переменную в вычислении значения, соответствующего ключу).

Во время обсуждения этого PEP, данный оператор стал неофициально известен как «моржовый оператор» (the walrus operator). Формальное имя конструкции — «Выражение присваивания» (согласно заголовку PEP: Assignment Expressions), но она может упоминаться, как «Именованные выражения» (Named Expressions). Например, эталонная реализация в CPython использует именно это название.

Обоснование

Именование является важной частью программирования, которая позволяет использовать «описательное» имя вместо более длинного выражения, а также упрощает повторное использование значений. В настоящее время это возможно сделать лишь в виде инструкции, что делает эту операцию недоступной при генерации списков (list comprehension), а также в других выражениях.

Кроме того, именование частей большого выражения может помочь при интерактивной отладке, предоставив инструменты отображения подсказок и промежуточных результатов. Без возможности захвата результатов вложенных выражений, потребуется изменение исходного кода, но используя выражения присваивания вам достаточно вставить несколько «маркеров» вида «имя := выражение». Это устраняет лишний рефакторинг, а значит снижает вероятность непреднамеренного изменения кода в процессе отладки (частая причина Heisenbugs [прим. гейзенбаги — ошибки, которые меняют свойства кода во время отладки и могут неожиданно проявиться в продакшене] ), а также данный код будет более понятен другому программисту.

Важность реального кода

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

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

Тем не менее, для таких примеров есть хорошее применение: они помогают уточнить предполагаемую семантику. Поэтому мы приведём ниже некоторые из них. Однако, чтобы быть убедительными, примеры должны основываться на реальном коде, который был написан без размышлений об этом PEP-е. То есть код, являющийся частью реально полезного приложения (без разницы: большое оно, или маленькое). Тим Питерс очень сильно помог нам, просмотрев свои личные репозитории и выбрав примеры написанного им кода, которые (по его мнению) стали бы более понятным, если их переписать (без фанатизма) с использованием выражений присваивания. Его вывод таков: текущие изменения привнесли бы скромное, но явное улучшение в нескольких битах его кода.

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

Показательный случай: Гвидо нашел несколько иллюстративных моментов, когда программист повторяет подвыражение (тем самым замедляя программу), но экономит лишнюю строку кода. Например, вместо того, чтобы писать:

match = re.match(data)
group = match.group(1) if match else None

Программисты предпочитали такой вариант:

group = re.match(data).group(1) if re.match(data) else None

Вот ещё пример, показывающий, что программисты иногда готовы сделать больше работы, чтобы сохранить «прежний уровень» отступов:

match1 = pattern1.match(data)
match2 = pattern2.match(data)
if match1:
    result = match1.group(1)
elif match2:
    result = match2.group(2)
else:
    result = None

Этот код вычисляет pattern2, даже если pattern1 уже совпал (в этом случае второе под-условие никогда не выполнится). Поэтому следующее решение является более эффективным, но менее привлекательным:

match1 = pattern1.match(data)
if match1:
    result = match1.group(1)
else:
    match2 = pattern2.match(data)
    if match2:
        result = match2.group(2)
    else:
        result = None

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

В большинстве случаев, где в Python используются произвольные выражения (arbitrary expressions), теперь можно применять выражения присваивания. Они имеют форму NAME := expr, где expr — любое допустимое выражение Python, кроме кортежа без скобок (unparenthesized tuple), а NAME — идентификатор. Значение такого выражения совпадает с исходным, но дополнительным эффектом является присвоение значения целевому объекту:

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

# A loop that can't be trivially rewritten using 2-arg iter()
while chunk := file.read(8192):
   process(chunk)

# Reuse a value that's expensive to compute
[y := f(x), y**2, y**3]

# Share a subexpression between a comprehension filter clause and its output
filtered_data = [y for x in data if (y := f(x)) is not None]

Исключительные случаи

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

  • Выражения присваивания, не заключённые в скобки, запрещены на «верхнем» уровне:
    y := f(x)  # НЕДОПУСТИМО
    (y := f(x))  # Сработает, но не рекомендуется

    Это правило упростит программисту выбор между оператором присваивания и выражением присваивания — не будет существовать синтаксической ситуации, в которой оба варианта равноценны.

  • Не заключенные в скобки выражения присваивания запрещены в правой части каскадного присваивания. Пример:
    y0 = y1 := f(x)  # НЕДОПУСТИМО
    y0 = (y1 := f(x))  # Сработает, но не рекомендуется

    Не заключенные в скобки выражения присваивания запрещены в значениях ключевого аргумента при вызове функции. Пример:

    foo(x = y := f(x))  # НЕДОПУСТИМО
    foo(x=(y := f(x)))  # Возможно, хотя и сбивает с толку

    Опять же, такой механизм уменьшает у людей желание окончательно запутать и так сложный синтаксический анализ ключевых аргументов.

  • Не заключенные в скобки выражения присваивания запрещены в значениях аргумента по умолчанию. Пример:
    def foo(answer = p := 42):  # НЕДОПУСТИМО
        ...
    def foo(answer=(p := 42)):  # Valid, though not great style
        ...

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

  • Не заключенные в скобки выражения присваивания запрещены в качестве аннотаций для аргументов, возвращаемых значений и присваиваний. Пример:
    def foo(answer: p := 42 = 5):  # НЕДОПУСТИМО
        ...
    def foo(answer: (p := 42) = 5):  # Разрешено, но бесполезно
        ...

    Рассуждения по поводу введения этого правила аналогичны предыдущим: код, состоящий из комбинации операторов «=» и «:=» трудно правильно понять.

  • Не заключенные в скобки выражения присваивания запрещены в лямбда-функциях. Пример:
    (lambda: x := 1) # НЕДОПУСТИМО
    lambda: (x := 1) # Разрешено, но бесполезно
    (x := lambda: 1) # Разрешено
    lambda line: (m := re.match(pattern, line)) and m.group(1) # Valid

    Лямбда-функция имеет приоритет более высокий, чем «:=». Удобное присваивание лямбды к переменной здесь важнее. В случаях, когда переменная используется несколько раз, вам и так (наверняка) понадобятся скобки, потому это ограничение не сильно повлияет на ваш код.

  • Выражения присваивания внутри f-строк требуют скобок. Пример:
    >>> f'{(x:=10)}'  # Разрешено, выражение присваивания
    '10'
    >>> x = 10
    >>> f'{x:=10}'    # Разрешено, будет отформатировано, как '=10'
    '        10'

    Это показывает, что не всё выглядящее, как оператор присваивания в f-строке, является таковым. Парсер f-строки использует символ «:» для указания параметров форматирования. Чтобы сохранить обратную совместимость, при использовании оператора присваивания внутри f-строк он должен быть заключен в скобки. Как отмечено в примере выше, такое использование оператора присваивания не рекомендуется.

Область видимости

Выражение присваивания не вводит новую область видимости. В большинстве случаев область видимости, в которой будет создана переменная, не требует пояснений: она будет текущей. Если прежде переменная использовала ключевые слова nonlocal или global, тогда выражение присваивания учтёт это. Только лямбда (будучи анонимным определением функции) считается для этих целей отдельной областью видимости.

Существует один особый случай: выражение присваивания, встречающееся в генераторах списков, множеств, словарей или же в самих «выражениях генераторах» (ниже все вместе именуемые «генераторами» (comprehensions) ), привязывает переменную к области видимости, которая содержит генератор, соблюдая модификатор globab или nonglobal, если таковой существует.

Обоснование для этого особого случая двояко. Во-первых, это позволяет нам удобно захватывать «участника» в выражениях any () и all(), например:

if any((comment := line).startswith('#') for line in lines):
    print("First comment:", comment)
else:
    print("There are no comments")

if all((nonblank := line).strip() == '' for line in lines):
    print("All lines are blank")
else:
    print("First non-blank line:", nonblank)

Во-вторых, это предоставляет компактный способ обновления переменной из генератора, например:

# Compute partial sums in a list comprehension
total = 0
partial_sums = [total := total + v for v in values]
print("Total:", total)

Однако имя переменной из выражения присваивания не может совпадать с именем, которое уже используется в генераторах циклом for для итерации. Последние имена являются локальными по отношению к генератору, в котором появляются. Было бы противоречиво, если бы выражения присваивания ссылались ещё и к области видимости внутри генератора.

Например, [i: = i + 1 for i in range(5)] недопустимо: цикл for устанавливает, что i является локальной для генератора, но часть «i := i+1» настаивает на том, что i является переменной из внешней области видимости. По той же причине следующие примеры не сработают:


[[(j := j) for i in range(5)] for j in range(5)] # НЕДОПУСТИМО
[i := 0 for i, j in stuff]                       # НЕДОПУСТИМО
[i+1 for i in (i := stuff)]                      # НЕДОПУСТИМО

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

[False and (i := 0) for i, j in stuff]     # НЕДОПУСТИМО
[i for i, j in stuff if True or (j := 1)]  # НЕДОПУСТИМО

# [прим. для новичков. Из-за "ленивой" реализации логических 
# операторов, второе условие никогда не вычислится в обоих
# случаях, ведь результат заранее известен, но ошибка будет]

Для тела генератора (часть перед первым ключевым словом «for») и выражения-фильтра (часть после «if» и перед любым вложенным «for») это ограничение применяется исключительно к именам перемененных, которые одновременно используются в качестве итерационных переменных. Как мы уже сказали, Лямбда-выражения вводят новую явную область видимости функции и следовательно могут использоваться в выражениях генераторов без дополнительных ограничений. [прим. опять же, кроме таких случаев: [i for i in range(2, (lambda: (s:=2)() ))] ]

Из-за конструктивных ограничений в эталонной реализации (анализатор таблицы символов не может распознать, используются ли имена из левой части генератора в оставшейся части, где находится итерируемое выражение), поэтому выражения присваивания полностью запрещены как часть итерируемых (в части после каждого «in» и перед любым последующим ключевым словом «if» или «for»). То есть все эти случаи недопустимы:

[i+1 for i in (j := stuff)]                    # НЕДОПУСТИМО
[i+1 for i in range(2) for j in (k := stuff)]  # НЕДОПУСТИМО
[i+1 for i in [j for j in (k := stuff)]]       # НЕДОПУСТИМО
[i+1 for i in (lambda: (j := stuff))()]        # НЕДОПУСТИМО

Еще одно исключение возникает, когда выражение присваивания применяется в генераторах, которые находятся в области видимости класса. Если при использовании выше перечисленных правил должно произойти создание перемеренной в области видимости класса, то такое выражение присваивания недопустимо и приведёт к возникновению SyntaxError:

class Example:
    [(j := i) for i in range(5)]  # НЕДОПУСТИМО

(Причиной последнего исключения является неявная область видимости функции, созданной генератором — в настоящее время нет runtime механизма для функций, чтобы сослаться на переменную, расположенную в области видимости класса, и мы не хотим добавлять такой механизм. Если эта проблема когда-либо будет решена, то этот особый случай (возможно) будет удалён из спецификации выражений присваивания. Обратите внимание, что эта проблема возникнет, даже если вы раннее создали переменную в области видимости класса и пытаетесь изменить её выражением присваивания из генератора.)

Смотрите приложение B для примеров того, как выражения присваивания находящиеся в генераторах, преобразуются в эквивалентный код.

Относительный приоритет :=

Оператор := группируется сильнее, чем запятая во всех синтаксических позициях где это возможно, но слабее, чем все другие операторы, включая or, and, not, и условные выражения (A if C else B). Как следует из раздела «Исключительные случаи» выше, выражения присваивания никогда не работают на том же «уровне», что и классическое присваивание =. Если требуется другой порядок операций, используйте круглые скобки.

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

x := 0 # ЗАПРЕЩЕНО

(x := 0) # Рабочая альтернатива

x = y := 0 # ЗАПРЕЩЕНО

x = (y := 0) # Рабочая альтернатива

len(lines := f.readlines()) # Разрешено

foo(x := 3, cat='vector') # Разрешено

foo(cat=category := 'vector') # ЗАПРЕЩЕНО

foo(cat=(category := 'vector')) # Рабочая альтернатива

Большинство приведенных выше «допустимых» примеров не рекомендуется использовать на практике, поскольку люди, быстро просматривающие ваш исходный код, могут не правильно понять его смысл. Но в простых случаях это разрешено:

# Valid
if any(len(longline := line) >= 100 for line in lines):
    print("Extremely long line:", longline)

Этот PEP рекомендует абсолютно всегда ставить пробелы вокруг :=, аналогично рекомендации PEP 8 для = для классического присваивания. (Отличие последней рекомендации в том, что она запрещает пробелы вокруг =, который используется для передачи ключевых аргументов функции.)

Изменение порядка вычислений.

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

  • В генераторах словарей {X: Y for …}, Y в настоящее время вычисляется перед X. Мы предлагаем изменить это так, чтобы X вычислялся до Y. (В классическом dict, таком как {X: Y}, а также в dict((X, Y) for …) это уже реализовано. Поэтому и генераторы словарей должны соответствовать этому механизму)

Различия между выражениями присваивания и инструкциями присваивания.

Что наиболее важно, «:=» является выражением, а значит его можно использовать в случаях, когда инструкции недопустимы, включая лямбда-функции и генераторы. И наоборот, выражения присваивания не поддерживают расширенный функционал, который можно использовать в инструкциях присваивания:

  • Каскадное присваивание не поддерживается на прямую
    x = y = z = 0  # Equivalent: (z := (y := (x := 0)))
  • Отдельные «цели», кроме простого имени переменной NAME, не поддерживаются:
    # No equivalent
    a[i] = x
    self.rest = []
  • Функционал и приоритет «вокруг» запятых отличается:
    x = 1, 2  # Sets x to (1, 2)
    (x := 1, 2)  # Sets x to 1
  • Распаковка и упаковка значений не имеют «чистую» эквивалентность или вообще не поддерживаются
    # 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)

Спецификация изменяется во время реализации

Следующие изменения были сделаны на основе полученного опыта и дополнительного анализа после первого написания данного PEP и перед выпуском Python 3.8:

  • Для обеспечения согласованности с другими подобными исключениями, а также чтобы не вводить новое название, которое не обязательно будет удобно для конечных пользователей, первоначально предложенный подкласс TargetScopeError для SyntaxError был убран и понижен до обычного SyntaxError. [3]
  • Из-за ограничений в анализе таблицы символов CPython, эталонная реализация выражения присваивания вызывает SyntaxError для всех случаев использования внутри итераторов. Раньше это исключение возникало только если имя создаваемой переменной совпадало с тем, которое уже используется в итерационном выражении. Это может быть пересмотрено при наличии достаточно убедительных примеров, но дополнительная сложность кажется нецелесообразной для чисто «гипотетических» вариантов использования.

Примеры

Примеры из стандартной библиотеки Python

site.py

env_base используется только в условии, поэтому присваивание можно поместить в if, как «заголовок» логического блока.

  • Текущий код:
    env_base = os.environ.get("PYTHONUSERBASE", None)
    if env_base:
        return env_base
  • Улучшенный код:
    if env_base := os.environ.get("PYTHONUSERBASE", None):
        return env_base

_pydecimal.py

Вы можете избегать вложенных if, тем самым удалив один уровень отступов.

  • Текущий код:
    if self._is_special:
        ans = self._check_nans(context=context)
        if ans:
            return ans
  • Улучшенный код:
    if self._is_special and (ans := self._check_nans(context=context)):
        return ans

copy.py

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

  • Текущий код:
    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(deep)copyable object of type %s" % cls)
  • Улучшенный код:
    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(deep)copyable object of type %s" % cls)

datetime.py

tz используется только для s += tz. Перемещение его внутрь if помогает показать его логическую область использования.

  • Текущий код:
    s = _format_time(self._hour, self._minute,
                     self._second, self._microsecond,
                     timespec)
    tz = self._tzstr()
    if tz:
        s += tz
    return s
  • Улучшенный код:
    s = _format_time(self._hour, self._minute,
                     self._second, self._microsecond,
                     timespec)
    if tz := self._tzstr():
        s += tz
    return s

sysconfig.py

Вызов fp.readline(), как «условие» в цикле while ( а также вызов метода .match() ) в условии if делает код более компактным, не усложняя его понимание.

  • Текущий код:
    while True:
        line = fp.readline()
        if not line:
            break
        m = define_rx.match(line)
        if m:
            n, v = m.group(1, 2)
            try:
                v = int(v)
            except ValueError:
                pass
            vars[n] = v
        else:
            m = undef_rx.match(line)
            if m:
                vars[m.group(1)] = 0
  • Улучшенный код:
    while line := fp.readline():
        if m := define_rx.match(line):
            n, v = m.group(1, 2)
            try:
                v = int(v)
            except ValueError:
                pass
            vars[n] = v
        elif m := undef_rx.match(line):
            vars[m.group(1)] = 0

Упрощение генераторов списков

Теперь генератор списка может эффективно фильтроваться путем «захвата» условия:

results = [(x, y, x/y) for x in input_data if (y := f(x)) > 0]

После этого переменная может быть повторно использована в другом выражении:

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

Ещё раз обратите внимание, что в обоих случаях переменная y находится в той же области видимости, что и переменные result и stuff.

«Захват» значений в условиях

Выражения присваивания могут быть эффективно использованы в условиях оператора if или while:

# Loop-and-a-half
while (command := input("> ")) != "quit":
    print("You entered:", command)

# Capturing regular expression match objects
# See, for instance, Lib/pydoc.py, which uses a multiline spelling
# of this effect
if match := re.search(pat, text):
    print("Found:", match.group(0))
# The same syntax chains nicely into 'elif' statements, unlike the
# equivalent using assignment statements.
elif match := re.search(otherpat, text):
    print("Alternate found:", match.group(0))
elif match := re.search(third, text):
    print("Fallback found:", match.group(0))

# Reading socket data until an empty string is returned
while data := sock.recv(8192):
    print("Received data:", data)

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

Fork

Пример из низкоуровневого мира UNIX: [прим. Fork() — системный вызов в Unix-подобных операционных системах, создающий новый под-процесс, по отношению к родительскому.]

if pid := os.fork():
    # Parent code
else:
    # Child code

Отклоненные альтернативны

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

Изменение области видимости для генераторов

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

Альтернативные варианты написания

В целом, предложенные выражения присваивания имеют ту же семантику, но пишутся по-другому.

  1. EXPR as NAME:
    stuff = [[f(x) as y, x/y] for x in range(5)]

    Так как конструкция EXPR as NAME уже имеет семантический смысл в выражениях import, except и with, это могло создать ненужную путаницу и некоторые ограничения (например, запрет выражения присваивания внутри заголовков этих конструкций).

    (Обратите внимание, что «with EXPR as VAR» не просто присваивает значение EXPR в VAR, а вызывает EXPR.__enter__() и уже после присваивает полученный результат в VAR.)

    Дополнительные причины, чтобы предпочесть «:=» выше предложенному написанию:

    • В том случае, если if f(x) as y не бросится вам в глаза, то его можно ​​случайно прочитать как if f x blah-blah, и визуально такая конструкция слишком похожа на if f(x) and y.
    • Во всех других ситуациях, когда as разрешено, даже читателям со средними навыками приходится прочитывать всю конструкцию от начала, чтобы посмотреть на ключевое слово:
      • import foo as bar
      • except Exc as var
      • with ctxmgr() as var

      И наоборот, as не относится к оператором if или while и мы преднамеренно создаём путаницу, допуская использование as в «не родной» для него среде.

    • Также существует «параллель» соответствия между
      • NAME = EXPR
      • if NAME := EXPR

      Это усиливает визуальное распознавание выражений присваивания.

  2. EXPR -> NAME
    stuff = [[f(x) -> y, x/y] for x in range(5)]

    Этот синтаксис основан на таких языках, как R и Haskell, ну и некоторых программируемых калькуляторах. (Обратите внимание, что направление стрелки справа-налево y < — f (x) невозможно в Python, поскольку конструкция будет интерпретироваться как меньше-чем и унарный минус.) Данный синтаксис имеет небольшое преимущество перед «as» в том смысле, что не конфликтует с конструкциями import, except и with, но в остальном проблемы те же. Но эти проблемы совершенно не связано с другим использованием такой стрелки в Python (в аннотациях возвращаемого типа функции), а просто по сравнению с «:=» (которое восходит к Algol-58) стрелочки менее привычны для присваивания.

  3. Добавление оператора «точка» к именам локальных переменных
    stuff = [[(f(x) as .y), x/.y] for x in range(5)] # with "as"
    stuff = [[(.y := f(x)), x/.y] for x in range(5)] # with ":="

    Это позволит легко обнаруживать и устраняя некоторые формы синтаксической неоднозначности. Однако такое нововведение стало бы единственным местом в Python, где область видимости переменной закодирована в ее имени, что затрудняет рефакторинг.

  4. Добавление where: к любой инструкции для создания локальных имен:
    value = x**2 + 2*x where:
        x = spam(1, 4, 7, q)

    Порядок выполнения инвертирован (часть с отступом выполнится первой, а затем последует срабатывание «заголовка»). Это потребует введения нового ключевого слова, хотя возможно «перепрофилирование» другого (скорее всего with:). См. PEP 3150, где раннее обсуждался этот вопрос (предложенным там словом являлось given: ).

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

    Этот синтаксис меньше конфликтует с другими, чем as (если только не считать конструкции raise Exc from Exc), но в остальном сравним с ними. Вместо параллели с with expr as target: (что может быть полезно, но может и сбить с толку), этот вариант вообще не имеет параллелей ни с чем, но к удивлению лучше запоминается.

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

Один из самых популярных вариантов использования выражений присваивания — это операторы if и while. Вместо более общего решения, использование as улучшает синтаксис этих двух операторов, добавляя средство захвата сравниваемого значения:

if re.search(pat, text) as match:
    print("Found:", match.group(0))

Это прекрасно работает, но ТОЛЬКО, когда желаемое условие основано на «правильности» возвращаемого значения. Таким образом, данный способ эффективен для конкретных случаев (проверки совпадения регулярных выражений, чтения сокетов, возвращающее пустую строку, когда заканчивается выполнение), и совершенно бесполезен в более сложных случаях (например, когда условие равно f(x) < 0, и вы хотите сохранить значение f(x) ). Также это не имеет смысла в генераторах списков.

Преимущества: нет синтаксических неясностей. Недостатки: даже если пользоваться им только в операторах if/while, хорошо работает лишь в части случаев.

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

Другим распространенным вариантом использования выражения присваивания являются генераторы (list/set/dict и genexps). Как и выше, были сделаны предложения для конкретных решений.

  1. where, let, or given:
    stuff = [(y, x/y) where y = f(x) for x in range(5)]
    stuff = [(y, x/y) let y = f(x) for x in range(5)]
    stuff = [(y, x/y) given y = f(x) for x in range(5)]

    Этот способ приводит появлению подвыражения между циклом «for» и основным выражением. Он также вводит дополнительное ключевое слово языка, что может создать конфликты. Из трех вариантов, where является наиболее чистым и читабельным, но потенциальные конфликты всё ещё существуют (например, SQLAlchemy и numpy имеют свои методы where, также как и tkinter.dnd.Icon в стандартной библиотеке).

  2. with NAME = EXPR:
    stuff = [(y, x/y) with y = f(x) for x in range(5)]

    Всё тоже самое, как и в верхнем пункте, но используется ключевое слово with. Неплохо читается и не нуждается в дополнительном ключевом слове. Тем не менее, способ более ограничен и не может быть легко преобразован в «петлевой» цикл for. Имеет проблему языка C, где знак равенства в выражении теперь может создавать переменную, а не выполнять сравнение. Также возникает вопрос: «А почему «with NAME = EXPR:» не может быть использовано просто как выражение, само по себе?»

  3. with EXPR as NAME:
    stuff = [(y, x/y) with f(x) as y for x in range(5)]

    Похоже на второй вариант, но с использованием as, а не знака равенства. Синтаксически родственно другими видами присваивания промежуточных имён, но имеет те же проблемы с циклами for. Смысл при использованием ключевого слова with в генераторах и в качестве отдельной инструкции будет совершенно различным

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

Понижение приоритета оператора

Оператор := имеет два логических приоритета. Либо он должен иметь настолько низкий приоритет, насколько это возможно (наравне оператора присваивания). Либо должен иметь приоритет больший, чем операторы сравнения. Размещение его приоритета между операторами сравнения и арифметическими операциями (если быть точным: чуть ниже, чем побитовое ИЛИ) позволит при использовании операторов while и if в большинстве случаев обходиться без скобок, так как более вероятно, что вы хотите сохранить значение чего-либо до того, как выполнится сравнение над ним:

pos = -1
while pos := buffer.find(search_term, pos + 1) >= 0:
    ...

Как только find() возвращает -1, цикл завершается. Если := связывает операнды также свободно, как и =, то результат find() будет сначала «захвачен» в оператор сравнения и вернёт обычно значение True, либо False, которое менее полезно.

Хоть такое поведение и было бы удобно на практике во многих ситуациях, но его и было бы сложнее объяснить. А так мы можем сказать, что «оператор := ведет себя так же, как и оператор обычного присваивания». То есть приоритет для := был выбран максимально близко к оператору = (за исключением того, что := имеет приоритет выше, чем запятая).

Даёшь запятые справа

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

(point := (x, y))
(point := x, y)

(В текущей версии стандарта последняя запись будет эквивалентна выражению ((point: = x), y) .)

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

foo (x: = 1, y)
foo (x: = (1, y))

И мы получаем единственный менее запутанный выход: сделать оператор := меньшего приоритета, чем запятую.

Всегда требующие скобки

Было предложено всегда заключать в скобки выражения присваивания. Это избавило бы нас от многих двусмысленностей. И действительно, скобки часто будут необходимы, чтобы извлечь желаемое значение. Но в следующих случаях наличие скобок явно показалось нам излишними:

# Top level in if
if match := pattern.match(line):
    return match.group(1)

# Short call
len(lines := f.readlines())

Частые возражения

Почему бы просто не превратить инструкции присваивания в выражения?

C и подобные ему языки определяют оператор = как выражение, а не инструкцию, как это делает Python. Это позволяет осуществлять присваивание во многих ситуациях, включая места, где происходит сравнение переменных. Синтаксическое сходство между if (x == y) и if (x = y) противоречит их резко отличающейся семантике. Таким образом, этот PEP вводит оператор := для уточнения их различия.

Зачем заморачиваться с выражениями присваивания, если существуют инструкции присваивания?

Две этих формы имеют различные гибкие возможности. Оператор := можно использовать внутри большего выражения, а в операторе = может использоваться «семейством мини-операторов» по типу «+=». Также = позволяет присваивать значения по атрибутам и индексам.

Почему бы не использовать локальную область видимости и предотвратить загрязнение пространства имен?

Предыдущие версии этого стандарта включали в себя реальную локальную область действия (ограниченную одним оператором) для выражений присваивания, предотвращая утечку имен и загрязнения пространства имен. Несмотря на то, что в ряде ситуаций это давало определенное преимущество, во многих других это усложняет задачу, и выгоды не оправдываются преимуществами существующего подхода. Это сделано в интересах простоты языка. Вам больше не нужна эта переменная? Есть выход: удалите переменную через ключевое слово del или добавьте к её названию нижнее подчеркивание.

(Автор хотел бы поблагодарить Гвидо ван Россума и Кристофа Грота за их предложения по продвижению стандарта PEP в этом направлении. [2])

Рекомендации по стилю

Поскольку выражения присваивания иногда могут использоваться наравне с оператором присваивания, возникает вопрос, чему всё-таки отдавать предпочтение?.. В соответствии с другими соглашениями о стиле (такими, как PEP 8), существует две рекомендации:

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

Благодарность

Авторы этого стандарта хотели бы поблагодарить Ника Коглана (Nick Coghlan) и Стивена Д’Апрано (Steven D’Aprano) за их значительный вклад в этот PEP, а также членов Python Core Mentorship за помощь в реализации.

Приложение A: выводы Тима Петерса

Вот краткое эссе, которое Тим Питерс написал на данную тематику.

Мне не нравятся «замороченный» код, а также не нравится помещать концептуально не связанную логику в одну строку. Так, например, вместо:

i = j = count = nerrors = 0

Я предпочитаю писать:

i = j = 0
count = 0
nerrors = 0

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

mylast = mylast[1]
yield mylast[0]

Значительно лучше, чем это:

yield (mylast := mylast[1])[0]

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

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

Но такие случаи редки. Задача сохранения результата встречается очень часто, и «разреженное лучше, чем плотное» не означает, что «почти пустое лучше, чем разреженное» [прим. отсылка к Дзену пайтона]. Например, у меня есть много функций, которые возвращают None или 0, чтобы сообщить «У меня нет ничего полезного, но так как это часто происходит, я не хочу надоедать вам исключениями». По сути, этот механизм используется и в регулярных выражениях, которые возвращают None, когда нет совпадений. Поэтому в таком примере много кода:

result = solution(xs, n)
if result:
    # use result

Я считаю следующий вариант более понятным, и конечно же более удобным для чтения:

if result := solution(xs, n):
    # use result

Сначала я не придавал этому особого значения, но такая короткая конструкция появлялась настолько часто, что меня довольно скоро начало раздражать, что я не могу воспользоваться ею. Это меня удивило! [прим. видимо это было написано до того, как официально вышел Python 3.8]

Есть и другие случаи, когда выражения присваивания действительно «выстреливают». Вместо того, чтобы ещё раз порыться в моём коде, Кирилл Балунов (Kirill Balunov) привел прекрасный пример функции copy() из стандартной библиотеки copy.py:

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 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)

Простое использование выражений присваивания позволяет визуальной структуре кода подчеркнуть «плоскость» логики. А вот постоянно увеличивающийся отступ делает её неявной.

Вот ещё небольшой пример из моего кода, который очень порадовал меня, поскольку позволял поместить внутренне связанную логику в одну строку и удалить раздражающий «искусственный» уровень отступа. Это именно то, чего я хочу от оператора if и это позволяет упростить чтение. Следующий код:

diff = x - x_base
if diff:
    g = gcd(diff, n)
    if g > 1:
        return g

Превратился в:

if (diff := x - x_base) and (g := gcd(diff, n)) > 1:
    return g

Итак, в большинстве строк, где происходит присваивание переменной, я бы не использовал выражения присваивания. Но эта конструкция настолько частая, что всё ещё есть много мест, где я бы воспользовался такой возможностью. В большинстве последних случаев я немного выиграл, поскольку они часто появлялись. В оставшейся под-части это привело к средним или большим улучшениям. Таким образом, я бы использовал выражения присваивания гораздо чаще, чем тройной if, но и значительно реже, чем augmented assignment [прим. короткие варианты: *=, /=, += и т.д.].

Числовой пример

У меня есть еще один пример, который поразил меня раннее.

Если все переменные являются положительными целыми числами, а переменная a больше n-ого корня из x, то этот алгоритм возвращает «нижнее» округление n-го корня из x (и примерно удваивает количество точных битов за итерацию):

while a > (d := x // a**(n-1)):
    a = ((n-1)*a + d) // n
return a

Непонятно почему, но такой вариант алгоритма менее очевиден, нежели бесконечный цикл с условной веткой break (loop and a half). Также трудно доказать правильность этой реализации, не опираясь на математическое утверждение («среднее арифметическое — среднее геометрическое неравенство») и не зная некоторых нетривиальных вещей о том, как ведут себя вложенные функции округления в меньшую сторону. Но здесь уже проблема заключена в математике, а не в программировании.

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

while True:
    d = x // a**(n-1)
    if a <= d:
        break
    a = ((n-1)*a + d) // n
return a

Приложение B: Грубый интерпретатор кода для генераторов

В этом приложении делается попытка прояснить (хотя и не указать) правила, по которым должно происходить создание переменной в генераторных выражениях. Для ряда иллюстративных примеров мы покажем исходный код, где генератор заменяется эквивалентной ему функцией в комбинации с некоторыми «строительными лесами».

Поскольку [x for …] эквивалентно list(x for …), то примеры не теряют своей общности. И поскольку эти примеры предназначены лишь для разъяснения общих правил, они не претендуют на реалистичность.

Примечание: генераторы сейчас реализованы через создание вложенных функций-генераторов (подобных тем, которые приведены в этом приложении). В примерах показана новая часть, которая добавляет соответствующий функционал для работы с областью видимости выражений присваивания (такой областью видимости, как если бы присваивание было выполнено в блоке, содержащем самый внешний генератор). Для упрощения «вывода типов» (type inference), эти иллюстративные примеры не учитывают, что выражения присваивания являются необязательными (но они учитывают, в какой область видимости будет находиться переменная, созданная внутри генератора).

Давайте сначала вспомним, какой код создаётся «под капотом» для генераторов без выражений присваивания:

  • Исходный код (EXPR чаще всего использует в себе переменную VAR):
    def f():
        a = [EXPR for VAR in ITERABLE]
  • Преобразованный код (давайте не будем беспокоиться о конфликтах имен):
    def f():
        def genexpr(iterator):
            for VAR in iterator:
                yield EXPR
        a = list(genexpr(iter(ITERABLE)))

Давайте добавим простое выражение присваивания.

  • Исходный код:
    def f():
        a = [TARGET := EXPR for VAR in ITERABLE]
    
  • Преобразованный код:
    def f():
        if False:
            TARGET = None  # Dead code to ensure TARGET is a local variable
        def genexpr(iterator):
            nonlocal TARGET
            for VAR in iterator:
                TARGET = EXPR
                yield TARGET
        a = list(genexpr(iter(ITERABLE)))

Теперь давайте добавим инструкцию global TARGET в объявление функции f().

  • Исходный код:
    def f():
        global TARGET
        a = [TARGET := EXPR for VAR in ITERABLE]
    
  • Преобразованный код:
    def f():
        global TARGET
        def genexpr(iterator):
            global TARGET
            for VAR in iterator:
                TARGET = EXPR
                yield TARGET
        a = list(genexpr(iter(ITERABLE)))

Или наоборот, давайте добавим nonlocal TARGET в объявление функции f().

  • Исходный код:
    def g():
        TARGET = ...
        def f():
            nonlocal TARGET
            a = [TARGET := EXPR for VAR in ITERABLE]
    
  • Преобразованный код:
    def g():
        TARGET = ...
        def f():
            nonlocal TARGET
            def genexpr(iterator):
                nonlocal TARGET
                for VAR in iterator:
                    TARGET = EXPR
                    yield TARGET
            a = list(genexpr(iter(ITERABLE)))

И наконец, давайте вложим два генератора.

  • Исходный код:
    def f():
        a = [[TARGET := i for i in range(3)] for j in range(2)]
        # I.e., a = [[0, 1, 2], [0, 1, 2]]
        print(TARGET)  # prints 2
    
  • Преобразованный код:
    def f():
        if False:
            TARGET = None
        def outer_genexpr(outer_iterator):
            nonlocal TARGET
            def inner_generator(inner_iterator):
                nonlocal TARGET
                for i in inner_iterator:
                    TARGET = i
                    yield i
            for j in outer_iterator:
                yield list(inner_generator(range(3)))
        a = list(outer_genexpr(range(2)))
        print(TARGET)

Приложение C: Никаких изменений в семантике области видимости

Обратите внимание, что в Python семантика области видимости не изменилась. Области видимости локальных функций по-прежнему определяются во время компиляции и имеют неопределенную временную протяженность во время выполнения (замыкания). Пример:

a = 42
def f():
    # `a` is local to `f`, but remains unbound
    # until the caller executes this genexp:
    yield ((a := i) for i in range(3))
    yield lambda: a + 100
    print("done")
    try:
        print(f"`a` is bound to {a}")
        assert False
    except UnboundLocalError:
        print("`a` is not yet bound")

Тогда:

>>> results = list(f()) # [genexp, lambda]
done
`a` is not yet bound
# The execution frame for f no longer exists in CPython,
# but f's locals live so long as they can still be referenced.
>>> list(map(type, results))
[<class 'generator'>, <class 'function'>]
>>> list(results[0])
[0, 1, 2]
>>> results[1]()
102
>>> a
42

Ссылки

  1. Доказательство реализации концепции
  2. Обсуждение семантики выражений присваивания (с VPN туго, но грузится)
  3. Обсуждение TargetScopeError в PEP 572 (грузится аналогично предыдущему)

Авторские права

Этот документ был размещен в открытом доступе.

Источник: github.com/python/peps/blob/master/pep-0572.rst

Моя часть

Для начала, подведём итоги:

  • Чтобы люди не говнокодили убрать смысловую двойственность, во многих «классических» местах, где можно было бы использовать и «=» и «:=» есть ограничения, поэтому оператор «:=» нужно часто заключать в скобки. Эти случаи придётся просмотреть в разделе, описывающем базовое использование.
  • Приоритет выражений присваивания чуть выше, чем у запятой. Благодаря этому, при присваивании не образуются кортежи. Также это даёт возможность использовать оператор := при передаче аргументов в функцию.
  • Выражения присваивания, находящиеся в генераторах, используют ту область видимости, в которой находится генератор. Это позволяет сохранять значения для повторного использования. А вот в lambda функциях это не сработает, они создают свою «анонимную» область видимости.
  • Теперь и в генераторах словарей строго определён порядок вычислений: сначала считается ключ, а потом соответствующее ему значение
  • Нельзя изменить в генераторе через присваивание переменную, использующуюся в итераторе.
  • Можно отстрелить левую ногу при попытке через генератор с присваиванием изменить/создать переменную класса.
  • Можно отстрелить правую ногу, подставив выражение присваивания в итерационное выражение.

В итоге, я хочу сказать, что мне понравился новый оператор. Он позволяет писать более плоский код в условиях, «фильтровать» списки, а также (наконец-то) убрать «ту самую», одинокую строчку перед if. Если люди будут использовать выражения присваивания по назначению, то это будет очень удобный инструмент, который повысит читабельность и красоту кода (Хотя, такое можно сказать про любой функционал языка….)

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Как нужно переводить?


7.25%
Переводить максимально слово в слово
5


76.81%
Если переводчик разбирается в теме, то он может слегка изменить текст, чтобы было понятнее
53


15.94%
Переводчик — ваш главный товарищ. Он может изменять текст, добавлять свои пояснения пример, удалять что-то не нужное и т.д.
11

Проголосовали 69 пользователей.

Воздержались 9 пользователей.

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

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

Другой важной особенностью использования скобок в инструкции присваивания является улучшение читабельности кода. Скобки помогают организовать выражения и выделить группы операторов, что делает код более понятным для других программистов и для самого разработчика. Также, использование скобок может помочь избежать ошибок, связанных с неправильным определением порядка выполнения операций.

Особенности использования скобок в инструкции присваивания

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

Первое особенностью заключается в том, что скобки позволяют уточнить приоритет выполнения операций в инструкции присваивания. Когда в выражении присутствуют разные операторы, скобки помогают явно указать, какую часть выражения нужно вычислить в первую очередь. Таким образом, использование скобок способствует более точной интерпретации выражения и предотвращает возможные ошибки при выполнении.

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

Кроме того, использование скобок снижает вероятность возникновения ошибок и непредвиденного поведения программы. Если не использовать скобки в инструкции присваивания, то возникает риск, что определенные операции будут выполнены в неправильной последовательности или не в том порядке, который задумал программист. Скобки четко определяют, какой части выражения должны быть выполнены сначала, и исключают возможность неправильного выполнения программы. Это повышает надежность кода и предотвращает возможные ошибки в работе программы.

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

Улучшение читаемости кода

Использование скобок в инструкциях присваивания может значительно улучшить читаемость кода.

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

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

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

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

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

Один из ключевых элементов программирования — это присваивание значений переменным. И, как правило, в процессе присваивания мы используем скобки. Зачем это нужно и какие возможности они предоставляют? В этой статье мы разберемся с этим вопросом и рассмотрим примеры применения скобок в инструкции присваивания.

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

Например, рассмотрим следующее выражение: x = 2 * 3 + 4. Если не использовать скобки, то сначала выполнится умножение, а затем сложение, и результат будет равен 10. Но если мы используем скобки в этой инструкции присваивания, например, x = (2 * 3) + 4, то сначала выполнится операция внутри скобок, результатом которой будет 6, и затем будет выполнено сложение с 4, и в итоге переменная x будет равна 10.

Таким образом, использование скобок позволяет нам контролировать порядок выполнения операций в инструкции присваивания и, следовательно, получать ожидаемые результаты. Без скобок программы могут давать неправильные значения, что может привести к непредсказуемым последствиям. Поэтому, помните о важности скобок при работе с присваиванием значений переменным!

Содержание

  1. Зачем применяются скобки в инструкции присваивания: объяснение и примеры
  2. Разбор важности скобок в инструкции присваивания
  3. Основная цель использования скобок в инструкции присваивания
  4. Возможные проблемы, возникающие при отсутствии скобок в инструкции присваивания
  5. Преимущества использования скобок в инструкции присваивания
  6. Общая структура инструкции присваивания с применением скобок и без них
  7. Примеры использования скобок в инструкции присваивания

Зачем применяются скобки в инструкции присваивания: объяснение и примеры

Вот несколько причин, почему скобки применяются в инструкциях присваивания:

  1. Определение порядка операций: скобки позволяют определить, какие операции должны быть выполнены в первую очередь, а какие — во вторую. Например, выражение (2 + 3) * 4 означает, что сначала будет выполнено сложение 2 + 3, а затем результат будет умножен на 4.
  2. Управление приоритетом: скобки позволяют изменить порядок выполнения операций и управлять приоритетом. Например, если у вас есть выражение 2 * 3 + 4, оно будет оценено как (2 * 3) + 4, что равно 10. Однако, если вы используете скобки: 2 * (3 + 4), оно будет оценено как 2 * 7, что даст результат 14.
  3. Улучшение читаемости кода: использование скобок может сделать код более понятным и интуитивно понятным для других разработчиков. Вместо того, чтобы полагаться на порядок выполнения по умолчанию, использование скобок позволяет четко указать, какие операции должны быть выполнены первыми.

Приведем несколько примеров, чтобы продемонстрировать применение скобок в инструкциях присваивания:

  • x = (2 + 3) * 4; — в этом примере скобки используются для определения порядка выполнения операций: сначала выполняется сложение 2 + 3, а затем результат умножается на 4.
  • y = 2 * (3 + 4); — в этом примере скобки используются для изменения порядка выполнения операций: сначала выполняется сложение 3 + 4, а затем результат умножается на 2.
  • z = x * (y + 2); — в этом примере скобки используются для определения порядка выполнения операций: сначала выполняется сложение y + 2, а затем результат умножается на значение переменной x.

Таким образом, использование скобок в инструкциях присваивания позволяет контролировать порядок выполнения операций и управлять приоритетом, а также повышает читаемость кода.

Разбор важности скобок в инструкции присваивания

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

Важность скобок в инструкции присваивания может быть продемонстрирована на следующем примере:

x = 5 * 2 + 3;

В данном случае, без использования скобок, сначала будет выполнено умножение 5 на 2, а затем полученный результат будет сложен с 3. То есть, ответ будет равен 13.

Однако, если мы добавим скобки:

x = (5 * 2) + 3;

Теперь умножение будет выполнено первым, а затем результат будет сложен с 3. В данном случае, ответ будет равен 13.

Скобки можно использовать и для группировки операций с различными операндами:

y = (x + 3) * (x - 2);

В данном случае, значение переменной x будет сначала увеличено на 3, затем уменьшено на 2, и результаты будут перемножены. Скобки в данном случае позволяют точно определить, какие операции следует выполнить первыми.

Таким образом, использование скобок в инструкции присваивания позволяет контролировать порядок выполнения операций и группировать операции с различными операндами. Без использования скобок, результат вычислений может быть неправильным и привести к ошибкам в программе.

Основная цель использования скобок в инструкции присваивания

Скобки в инструкции присваивания играют важную роль в языке программирования, таком как HTML. Они используются для группировки и управления порядком выполнения операций.

Когда мы присваиваем значение переменной, используя оператор присваивания «=», скобки могут быть использованы для явного указания порядка выполнения операций. Они обеспечивают правильный порядок вычисления и могут изменять приоритет операций.

Например, если у нас есть следующая инструкция присваивания:

var result = (x + y) * z;

В данном случае, скобки указывают, что сначала должно быть выполнено сложение «x + y», а затем полученный результат умножен на значение переменной «z». Если бы скобок не было, операции выполнились бы в другом порядке, что могло бы привести к неправильному результату.

Использование скобок также позволяет вложенные операции. Например:

var result = (x + (y * z)) / w;

В этом примере, сначала внутри скобок происходит умножение «y * z», затем результат складывается с «x», и в конце полученное значение делится на «w». Порядок действий определяется скобками.

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

Возможные проблемы, возникающие при отсутствии скобок в инструкции присваивания

Отсутствие скобок в инструкции присваивания может привести к различным ошибкам и непредсказуемому поведению программы. Вот несколько возможных проблем:

  • Неявное приведение типов: если в инструкции присваивания отсутствуют скобки, то значение, которое выражение возвращает, может быть приведено к другому типу данных. Это может привести к потере точности, ошибкам округления или неправильным результатам.
  • Ошибки приоритета операций: без скобок в инструкции присваивания операции выполняются в определенном порядке, определяемом приоритетом операций. Если выражение содержит несколько операций с разным приоритетом, то результат может быть неожиданным.
  • Потеря данных: если выражение в инструкции присваивания не заключено в скобки, то только часть выражения может быть присвоена переменной. Это может привести к потере данных и неправильному результату выполнения программы.
  • Сложность чтения и понимания кода: отсутствие скобок может сделать код менее понятным и сложнее для чтения. При использовании скобок в инструкции присваивания код становится более ясным и понятным.

Правильное использование скобок в инструкции присваивания позволяет избежать этих проблем и обеспечить правильное выполнение программы.

Преимущества использования скобок в инструкции присваивания

  1. Установление порядка операций: скобки позволяют явно указать порядок выполнения операций в выражении. Это особенно важно, когда в выражении используются различные операции, такие как сложение, вычитание, умножение и деление. Скобки позволяют установить приоритет выполнения операций и избежать ошибок в вычислениях.
  2. Улучшение читаемости кода: использование скобок в инструкции присваивания позволяет более ясно и понятно указать, какие части кода должны быть выполнены в первую очередь. Это делает код более читаемым и улучшает его поддержку и модификацию.
  3. Повышение ясности выражений: скобки в инструкции присваивания помогают в избежании неоднозначности и позволяют точно указать, какие элементы выражения должны быть вычислены в первую очередь. Это особенно важно, когда в выражении используются переменные или функции.
  4. Объединение частей выражений: скобки позволяют объединить несколько выражений в одно и определить порядок их выполнения. Это упрощает код и делает его более компактным.

Пример использования скобок в инструкции присваивания:

int result = (a + b) * c;

В данном примере использование скобок позволяет сначала выполнить операцию сложения переменных «a» и «b», а затем результат умножить на переменную «c». Без использования скобок порядок выполнения операций мог бы быть неопределенным и код был бы менее читаемым и понятным.

Общая структура инструкции присваивания с применением скобок и без них

Инструкция присваивания в программировании используется для присвоения значения переменной. Часто в такой инструкции применяются скобки для уточнения порядка выполнения операций.

Общая структура инструкции присваивания с применением скобок выглядит следующим образом:

переменная = (значение или выражение);

Пример:

В данном примере значение переменной x будет равно сумме чисел 5 и 3, то есть 8.

Скобки в данном случае позволяют явно указать, что сначала должно быть выполнено сложение, и только потом результат будет присвоен переменной.

Также возможно использование инструкции присваивания без скобок:

переменная = значение или выражение;

Пример:

Результат будет аналогичным — значение переменной y будет равно 8. Однако в данном случае операция сложения выполнится вслед за присваиванием значения переменной.

Использование скобок в инструкции присваивания позволяет управлять порядком выполнения операций, делая код более понятным и предсказуемым.

Примеры использования скобок в инструкции присваивания

Скобки в инструкции присваивания используются в различных случаях для упрощения и ясности выражений. Рассмотрим несколько примеров:

  1. Использование скобок для задания приоритета операций:
  2. При выполнении сложных вычислений с использованием различных операторов (например, арифметических операторов) скобки позволяют задать порядок выполнения операций. Например:

    let result = (2 + 3) * 4;
    console.log(result); // 20
    
  3. Использование скобок для объединения переменных и литералов:
  4. Скобки также могут использоваться для объединения нескольких переменных, значений или литералов в одно выражение. Например:

    let message = "Hello " + (firstName + " " + lastName) + "!";
    console.log(message); // Hello John Doe!
    
  5. Использование скобок для явного указания порядка операций:
  6. В некоторых случаях скобки используются для явного указания порядка выполнения операций, даже если он уже задан по умолчанию. Например:

    let result = 10 / (2 + 3);
    console.log(result); // 2
    

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

Понравилась статья? Поделить с друзьями:

Это тоже интересно:

  • Зарядное устройство полюс 612т инструкция по эксплуатации
  • Зацеф инструкция по применению таблетки
  • Зарядное устройство полюс 912т инструкция по эксплуатации
  • Застройщик обязан передать инструкцию по эксплуатации
  • Зарядное устройство полюс 912 инструкция

  • Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии