Оператор Walrus: выражения присваивания в Python

Оглавление

Смотрите сейчас, к этому уроку прилагается соответствующий видеокурс, созданный командой Real Python. Посмотрите его вместе с письменным руководством, чтобы углубить свое понимание: Выражения присваивания в Python и использование оператора Walrus

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

К концу этого урока вы поймете, что:

  • Оператор walrus (:=) - это синтаксис для назначения переменных в выражениях в Python.
  • Выражения присваивания используют оператор walrus в Python.
  • Оператор walrus можно использовать для упрощения кода, например, в циклах или условных выражениях.
  • Выражения присваивания возвращают присвоенное значение, в отличие от обычных присваиваний.
  • Обычные задания не возвращают значений, чтобы предотвратить непреднамеренное поведение и ошибки.

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

Получите свой код: Нажмите здесь, чтобы загрузить бесплатный пример кода, который показывает, как использовать оператор walrus в Python.

<отметить класс="marker-highlight"> Пройдите тест: Проверьте свои знания с помощью нашего интерактивного теста ”Оператор Walrus: выражения присваивания в Python". По завершении вы получите оценку, которая поможет вам отслеживать прогресс в обучении:

<время работы/> The Walrus Operator: Python's Assignment Expressions

Интерактивная викторина

Оператор Walrus: выражения присваивания в Python

В этом тесте вы проверите свое понимание оператора walrus в Python. Этот оператор был представлен в Python 3.8, и его понимание может помочь вам написать более краткий и эффективный код.

Основы работы с моржами

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

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

Привет, Морж!

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

 1>>> walrus = False
 2>>> walrus
 3False
 4
 5>>> (walrus := True)
 6True
 7>>> walrus
 8True


В строке 1 показан традиционный оператор присваивания, в котором значение False присваивается walrus. Далее, в строке 5, вы используете выражение присваивания, чтобы присвоить значение True walrus. После обеих строк 1 и 5 вы можете ссылаться на присвоенные значения, используя имя переменной walrus.

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

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

Например, 1 + 2 - это выражение, которое вычисляет значение 3, в то время как number = 1 + 2 - это оператор присваивания, который не вычисляет значение. Хотя выполнение инструкции number = 1 + 2 не приводит к вычислению значения 3, она присваивает значению 3 значение number.

В Python вы часто видите простые операторы, такие как return операторы и import утверждений, а также составных утверждений, таких как if утверждения и определения функций. Все это утверждения, а не выражения.

Существует небольшая, но важная разница между двумя типами присваиваний с помощью переменной walrus. Выражение присваивания возвращает значение, в то время как традиционное присваивание этого не делает. Вы можете увидеть это в действии, когда REPL не выводит никакого значения после walrus = False в строке 1, но выводит True после выражения присваивания в строке 5.

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

Теперь у вас есть общее представление о том, что такое оператор := и что он может делать. Это оператор, используемый в выражениях присваивания, который может возвращать присваиваемое значение, в отличие от традиционных операторов присваивания. Чтобы углубиться и по-настоящему узнать о walrus operator, продолжайте читать, чтобы узнать, где вам следует, а где не следует его использовать.

Реализация

Как и большинство новых функций в Python, выражения присваивания были введены в рамках Предложения по улучшению Python (PEP). PEP 572 описывает мотивацию для внедрения оператора walrus, детали синтаксиса и примеры, в которых оператор := может быть использован для улучшения вашего кода.

Этот отрывок был первоначально написан Крисом Анджелико в феврале 2018 года. После бурного обсуждения ОПТОСОЗ 572 был принят Гвидо ван Россумом в июле 2018 года.

С тех пор Гвидо объявил, что он уходит со своего поста пожизненного доброжелательного диктатора (BDFL). С начала 2019 года язык Python управляется избранным руководящим советом вместо этого.

Оператор walrus был реализован Эмили Морхаус и стал доступен в первом альфа-релизе версии Python 3.8.

Мотивация

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

int x = 3, y = 8;
if (x = y) {
    printf("x and y are equal (x = %d, y = %d)", x, y);
}


Здесь значение if (x = y) будет равно true, и фрагмент кода выдаст значение x and y are equal (x = 8, y = 8). Это тот результат, которого вы ожидали? Вы пытались сравнить x и y. Как изменилось значение x с 3 на 8?

Проблема в том, что вы используете оператор присваивания (=) вместо оператора сравнения на равенство (==). В языке Си x = y - это выражение, которое принимает значение y. В этом примере x = y оценивается как 8, что считается верным в контексте if инструкции.

Взгляните на соответствующий пример на Python. Этот код вызывает SyntaxError:

x, y = 3, 8
if x = y:
    print(f"x and y are equal ({x = }, {y = })")


В отличие от примера на C, этот код на Python выдает явную ошибку вместо бага.

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

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

>>> walrus := True
  File "<stdin>", line 1
    walrus := True
           ^
SyntaxError: invalid syntax


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

>>> (walrus := True)  # Valid, but regular assignments are preferred
True


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

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

Варианты использования оператора Walrus

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

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

Вы увидите, как оператор walrus может помочь в каждой из этих ситуаций.

Отладка

Возможно, один из лучших вариантов использования оператора walrus - это отладка сложных выражений. Допустим, вы хотите найти расстояние между двумя точками на поверхности земли. Один из способов сделать это - использовать формулу хаверсина:

The haversine formula

θ представляет широту, а λ представляет долготу каждого местоположения. Чтобы продемонстрировать эту формулу, вы можете рассчитать расстояние между Осло (59.9° 10,8°северной широты) и Ванкувер (49.3° N 123,1°W) следующим образом:

>>> from math import asin, cos, radians, sin, sqrt

>>> # Approximate radius of Earth in kilometers
>>> rad = 6371

>>> # Locations of Oslo and Vancouver
>>> ϕ1, λ1 = radians(59.9), radians(10.8)
>>> ϕ2, λ2 = radians(49.3), radians(-123.1)

>>> # Distance between Oslo and Vancouver
>>> 2 * rad * asin(
...     sqrt(
...         sin((ϕ2 - ϕ1) / 2) ** 2
...         + cos(ϕ1) * cos(ϕ2) * sin((λ2 - λ1) / 2) ** 2
...     )
... )
...
7181.7841229421165


Как вы можете видеть, расстояние от Осло до Ванкувера составляет чуть менее 7200 километров.

Примечание: Исходный код Python обычно пишется с использованием UTF-8 Unicode. Это позволяет вам использовать в вашем коде греческие буквы, такие как ϕ и λ, что может быть полезно при переводе математических формул. Википедия показывает несколько альтернативных вариантов использования Юникода в вашей системе.

Хотя поддерживается UTF-8 (например, в строковых литералах), в именах переменных Python используется более ограниченный набор символов. Например, вы не можете использовать эмодзи при присвоении имен своим переменным. Это хорошее ограничение!

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

>>> 2 * rad * asin(
...     sqrt(
...         (ϕ_hav := sin((ϕ2 - ϕ1) / 2) ** 2)
...         + cos(ϕ1) * cos(ϕ2) * sin((λ2 - λ1) / 2) ** 2
...     )
... )
...
7181.7841229421165

>>> ϕ_hav
0.008532325425222883


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

Списки и словари

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

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

>>> numbers = [2, 8, 0, 1, 1, 9, 7, 7]

>>> description = {
...     "length": len(numbers),
...     "sum": sum(numbers),
...     "mean": sum(numbers) / len(numbers),
... }

>>> description
{'length': 8, 'sum': 35, 'mean': 4.375}


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

>>> numbers = [2, 8, 0, 1, 1, 9, 7, 7]

>>> num_length = len(numbers)
>>> num_sum = sum(numbers)

>>> description = {
...     "length": num_length,
...     "sum": num_sum,
...     "mean": num_sum / num_length,
... }

>>> description
{'length': 8, 'sum': 35, 'mean': 4.375}


Переменные num_length и num_sum используются только для оптимизации вычислений внутри словаря. Используя оператор walrus, вы можете сделать эту роль более понятной:

>>> numbers = [2, 8, 0, 1, 1, 9, 7, 7]

>>> description = {
...     "length": (num_length := len(numbers)),
...     "sum": (num_sum := sum(numbers)),
...     "mean": num_sum / num_length,
... }

>>> description
{'length': 8, 'sum': 35, 'mean': 4.375}


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

Примечание: Область действия переменных num_length и num_sum одинакова в примере с оператором walrus и в примере без него. Это означает, что в обоих примерах переменные доступны после определения description.

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

В следующем примере вы будете работать с простой реализацией утилиты wc для подсчета строк, слов и символов в текстовом файле:

wc.py
 1import pathlib
 2import sys
 3
 4for filename in sys.argv[1:]:
 5    path = pathlib.Path(filename)
 6    counts = (
 7        path.read_text().count("\n"),  # Number of lines
 8        len(path.read_text().split()),  # Number of words
 9        len(path.read_text()),  # Number of characters
10    )
11    print(*counts, path)

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

  • Строка 4 повторяет каждое имя файла, указанное пользователем. Список sys.argv содержит все аргументы, указанные в командной строке, начиная с названия вашего скрипта. Для получения дополнительной информации о sys.argv вы можете ознакомиться с Аргументами командной строки Python.
  • Строка 5 преобразует каждую строку имени файла в pathlib.Path объект. Сохранение имени файла в объекте Path позволяет вам удобно читать текстовый файл в следующих строках.
  • В строках с 6 по 10 создается набор значений, представляющих количество строк, слов и символов в одном текстовом файле.
  • Строка 7 считывает текстовый файл и вычисляет количество строк путем подсчета новых строк.
  • Строка 8 считывает текстовый файл и вычисляет количество слов путем разделения на пробелы.
  • Строка 9 считывает текстовый файл и вычисляет количество символов, определяя длину строки.
  • Строка 11 выводит все три параметра вместе с именем файла на консоль. Синтаксис *counts распаковывает кортеж counts. В этом случае оператор print() эквивалентен print(counts[0], counts[1], counts[2], path).

Чтобы увидеть wc.py в действии, вы можете использовать сам скрипт следующим образом:

$ python wc.py wc.py
11 32 307 wc.py


Другими словами, файл wc.py состоит из 11 строк, 32 слов и 307 символов.

Если вы внимательно посмотрите на эту реализацию, то заметите, что она далека от оптимальной. В частности, в ней трижды повторяется вызов path.read_text(). Это означает, что программа трижды считывает каждый текстовый файл. Вы можете использовать оператор walrus, чтобы избежать повторения:

wc.py
import pathlib
import sys

for filename in sys.argv[1:]:
    path = pathlib.Path(filename)
    counts = (
        (text := path.read_text()).count("\n"),  # Number of lines
        len(text.split()),  # Number of words
        len(text),  # Number of characters
    )
    print(*counts, path)

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

Программа работает по-прежнему, хотя количество слов и символов изменилось:

$ python wc.py wc.py
11 34 293 wc.py


Как и в предыдущих примерах, альтернативный подход заключается в определении text перед определением counts:

wc.py
import pathlib
import sys

for filename in sys.argv[1:]:
    path = pathlib.Path(filename)
    text = path.read_text()
    counts = (
        text.count("\n"),  # Number of lines
        len(text.split()),  # Number of words
        len(text),  # Number of characters
    )
    print(*counts, path)

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

Перечислите значения

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

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

slow_calculations.py
numbers = [7, 6, 1, 4, 1, 8, 0, 6]

results = [slow(num) for num in numbers if slow(num) > 0]

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

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

slow_calculations.py
results = []
for num in numbers:
    value = slow(num)
    if value > 0:
        results.append(value)

Это приведет к вызову slow() только один раз. К сожалению, код стал более подробным, и его назначение стало сложнее понять. Понимание списка явно сигнализировало о том, что вы создаете новый список, в то время как в явном цикле for это более скрыто, поскольку создание списка и использование .append() разделены несколькими строками кода. Кроме того, просмотр списка выполняется быстрее, чем повторные обращения к .append().

Вы можете закодировать некоторые другие решения, используя filter() выражение или что-то вроде двойного списка:

slow_calculations.py
# Using filter
results = filter(lambda value: value > 0, (slow(num) for num in numbers))

# Using a double list comprehension
results = [value for num in numbers for value in [slow(num)] if value > 0]

Хорошей новостью является то, что для каждого числа существует только один вызов slow(). Плохая новость заключается в том, что в обоих выражениях код стал менее читабельным.

Чтобы понять, что на самом деле происходит при использовании двойного списка, нужно изрядно поломать голову. По сути, второй оператор for используется только для присвоения имени value возвращаемому значению slow(num). К счастью, это похоже на то, что вы можете выполнить с помощью выражения присваивания!

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

slow_calculations.py
results = [value for num in numbers if (value := slow(num)) > 0]

Обратите внимание, что круглые скобки вокруг value := slow(num) обязательны. Эта версия эффективна и удобочитаема, а также хорошо передает цель кода.

Примечание: Вам необходимо добавить выражение присваивания в предложение if для понимания списка. Если вы попытаетесь определить value с помощью другого вызова slow(), то это не сработает:

>>> results = [(value := slow(num)) for num in numbers if value > 0]
NameError: name 'value' is not defined


Это вызовет NameError, потому что предложение if вычисляется перед выражением в начале понимания.

Далее рассмотрим несколько более сложный и практичный пример. Скажите, что вы хотите использовать Канал Real Python, чтобы найти названия последних эпизодов подкаста Real Python.

Вы можете использовать Real Python Feed Reader для загрузки информации о последних публикациях Real Python. Чтобы найти названия эпизодов подкаста, вы будете использовать сторонний пакет Parse. Начните с создания виртуальной среды и установки обоих пакетов:

(venv) $ python -m pip install realpython-reader parse


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

>>> from reader import feed

>>> feed.get_titles()
['The Walrus Operator: Python's Assignment Expressions',
 'Python News Roundup: August 2024',
 'The Real Python Podcast – Episode #216: The Black Python Devs Community',
 'Asynchronous Iterators and Iterables in Python',
 ...]


Названия подкастов начинаются с "The Real Python Podcast", поэтому здесь вы можете создать шаблон, который Parse сможет использовать для их идентификации:

>>> import parse

>>> pattern = parse.compile(
...     "The Real Python Podcast – Episode #{num:d}: {name}"
... )


Предварительная компиляция шаблона ускоряет последующие сравнения, особенно если вы хотите сопоставлять один и тот же шаблон снова и снова. Вы можете проверить, соответствует ли строка вашему шаблону, используя либо pattern.parse(), либо pattern.search():

>>> pattern.parse(
...     "The Real Python Podcast – Episode #216: "
...     "The Black Python Devs Community"
... )
...
<Result () {'num': 216, 'name': 'The Black Python Devs Community'}>


Обратите внимание, что Parse может определить номер эпизода подкаста и название эпизода. Эпизод количество преобразуется в число тип данных, потому что вы использовали :d спецификатор формата.

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

>>> import parse
>>> from reader import feed

>>> pattern = parse.compile(
...     "The Real Python Podcast – Episode #{num:d}: {name}"
... )

>>> podcasts = [
...     pattern.parse(title)["name"]
...     for title in feed.get_titles()
...     if pattern.parse(title)
... ]

>>> podcasts[:3]
['The Black Python Devs Community',
 'Using GraphQL in Django With Strawberry & Prototype Purgatory',
 'Build Captivating Display Tables in Python With Great Tables']


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

Как вы делали ранее, вы можете избежать двойной работы, переписав понимание списка, используя либо явный цикл for, либо двойное понимание списка. Однако использовать оператор walrus еще проще:

>>> podcasts = [
...     podcast["name"]
...     for title in feed.get_titles()
...     if (podcast := pattern.parse(title))
... ]


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

Примечание: У настоящего подкаста на Python есть своя отдельная RSS-лента, которую вам следует использовать, если вы хотите поиграться с информацией только о подкасте. Вы можете получить все названия эпизодов со следующим кодом:

>>> from reader import feed
>>> podcasts = feed.get_titles("https://realpython.com/podcasts/rpp/feed")


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

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

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

>>> import statistics

>>> statistics.mean(
...     title_length
...     for title in podcasts
...     if (title_length := len(title)) > 50
... )
61.525


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

В то время как циклы

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

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

walrus_quiz.py
question = "Do you use the walrus operator?"
valid_answers = {"yes", "Yes", "y", "Y", "no", "No", "n", "N"}

user_answer = input(f"\n{question} ")
while user_answer not in valid_answers:
    print(f"Please answer one of {', '.join(valid_answers)}")
    user_answer = input(f"\n{question} ")

Это работает, но содержит неудачное повторение двух одинаковых строк input(). Необходимо получить хотя бы один ответ от пользователя, прежде чем проверять, корректен он или нет. Затем у вас есть второй вызов input() внутри цикла while, чтобы запросить второй ответ в случае, если исходный user_answer был неверным.

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

walrus_quiz.py
# ...

while True:
    user_answer = input(f"\n{question} ")
    if user_answer in valid_answers:
        break
    print(f"Please answer one of {', '.join(valid_answers)}")

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

Выражения присваивания могут упростить такого рода циклы. В этом примере теперь вы можете вернуть проверку вместе с while там, где это имеет больше смысла:

walrus_quiz.py
# ...

while (user_answer := input(f"\n{question} ")) not in valid_answers:
    print(f"Please answer one of {', '.join(valid_answers)}")

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

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

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

walrus_quiz.py
import random
import string

QUESTIONS = {
    "What is the formal name of PEP 572?": [
        "Assignment Expressions",
        "Named Expressions",
        "The Walrus Operator",
        "The Colon Equals Operator",
    ],
    "Which one of these is an invalid use of the walrus operator?": [
        "[y**2 for x in range(10) if y := f(x) > 0]",
        "print(y := f(x))",
        "(y := f(x))",
        "any((y := f(x)) for x in range(10))",
    ],
}

num_correct = 0
for question, answers in QUESTIONS.items():
    correct = answers[0]
    random.shuffle(answers)

    coded_answers = dict(zip(string.ascii_lowercase, answers))
    valid_answers = sorted(coded_answers.keys())

    for code, answer in coded_answers.items():
        print(f"  {code}) {answer}")

    while (user_answer := input(f"\n{question} ")) not in valid_answers:
        print(f"Please answer one of {', '.join(valid_answers)}")

    if coded_answers[user_answer] == correct:
        print(f"Correct, the answer is {user_answer!r}\n")
        num_correct += 1
    else:
        print(f"No, the answer is {correct!r}\n")

print(f"You got {num_correct} correct out of {len(QUESTIONS)} questions")

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

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

<отметить класс="marker-highlight"> Пройдите тест: Проверьте свои знания с помощью нашего интерактивного теста ”Оператор Walrus: выражения присваивания в Python". По завершении вы получите оценку, которая поможет вам отслеживать прогресс в обучении:

<время работы/> The Walrus Operator: Python's Assignment Expressions

Интерактивная викторина

Оператор Walrus: выражения присваивания в Python

В этом тесте вы проверите свое понимание оператора walrus в Python. Этот оператор был представлен в Python 3.8, и его понимание может помочь вам написать более краткий и эффективный код.

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

Свидетели и контрпримеры

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

В этом разделе вы узнаете, как найти свидетелей при вызове any() используя умный трюк, который не сразу становится возможным без использования оператора walrus. В данном контексте свидетель - это элемент, который удовлетворяет проверке и возвращает значение any() True.

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

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

>>> cities = ["Vancouver", "Oslo", "Berlin", "Krakow", "Graz", "Belgrade"]


Вы можете использовать any() и all() для ответа на вопросы о ваших данных:

>>> # Does ANY city name start with "B"?
>>> any(city.startswith("B") for city in cities)
True

>>> # Does ANY city name have at least 10 characters?
>>> any(len(city) >= 10 for city in cities)
False

>>> # Do ALL city names contain "A" or "L"?
>>> all(set(city.lower()) & set("al") for city in cities)
True

>>> # Do ALL city names start with "B"?
>>> all(city.startswith("B") for city in cities)
False


В каждом из этих случаев any() и all() дают вам простые True или False ответы. Что, если вам также интересно посмотреть пример или контрпример с названиями городов? Было бы неплохо посмотреть, что является причиной вашего результата True или False:

  • Начинается ли название любого города на "B"?

    Да, потому что "Berlin" начинается с "B".

  • Сделать все названия городов начинаются на "B"?

    Нет, потому что "Oslo" начинается не с "B".

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

Захват свидетеля для выражения any() в более ранних версиях Python не был интуитивно понятным. Если вы вызывали any() по списку, а затем поняли, что вам также нужен свидетель, вам, как правило, нужно переписать свой код:

>>> witnesses = [city for city in cities if city.startswith("B")]

>>> if witnesses:
...     print(f"{witnesses[0]} starts with B")
... else:
...     print("No city name starts with B")
...
Berlin starts with B


Здесь вы сначала записываете все названия городов, которые начинаются с "B". Затем, если есть хотя бы одно такое название города, вы выводите первое название города, начинающееся с "B". Обратите внимание, что здесь вы на самом деле не используете any(), хотя выполняете аналогичную операцию с пониманием списка.

Используя оператор :=, вы можете найти свидетелей непосредственно в ваших any() выражениях:

>>> if any((witness := city).startswith("B") for city in cities):
...     print(f"{witness} starts with B")
... else:
...     print("No city name starts with B")
...
Berlin starts with B


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

Примечание: Если вы хотите проверить, начинаются ли все названия городов на букву "B", то вы можно поискать контрпример, заменив any() на all() и обновив функции print(), чтобы сообщить о первом элементе, который не прошел проверку.

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

>>> def starts_with_b(name):
...     print(f"Checking {name}: {(result := name.startswith('B'))}")
...     return result
...

>>> any(starts_with_b(city) for city in cities)
Checking Vancouver: False
Checking Oslo: False
Checking Berlin: True
True


Обратите внимание, что any() на самом деле проверяет не все элементы в cities. Он проверяет элементы только до тех пор, пока не найдет тот, который удовлетворяет условию. Объединение операторов := и any() работает путем итеративного присвоения каждому проверяемому элементу значения witness. Однако сохраняется только последний такой элемент, который показывает, какой элемент был проверен пользователем в последний раз. any().

Даже если any() возвращает False, свидетель найден:

>>> any(len(witness := city) >= 10 for city in cities)
False

>>> witness
'Belgrade'


Однако в этом случае witness не дает никакой информации. 'Belgrade' не содержит десяти или более символов. В качестве свидетеля отображается только то, какой элемент был оценен последним.

Синтаксис оператора Walrus

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

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

Как вы видели в начале этого руководства, вы не можете использовать простое выражение присваивания для присвоения значения:

>>> walrus := True
  File "<stdin>", line 1
    walrus := True
           ^
SyntaxError: invalid syntax


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

>>> (walrus := True)
True


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

В PEP 572 приведены несколько других примеров, в которых оператор := либо запрещен, либо не рекомендуется. Все приведенные ниже примеры вызывают SyntaxError:

>>> lat = lon := 0
SyntaxError: invalid syntax

>>> angle(phi = lat := 59.9)
SyntaxError: invalid syntax

>>> def distance(phi = lat := 0, lam = lon := 0):
SyntaxError: invalid syntax


Во всех этих случаях лучше использовать =. Следующие примеры аналогичны и представляют собой юридический код. Однако оператор walrus не улучшает ваш код ни в одном из этих случаев:

>>> lat = (lon := 0)  # Discouraged

>>> angle(phi = (lat := 59.9))  # Discouraged

>>> def distance(phi = (lat := 0), lam = (lon := 0)):  # Discouraged
...     pass
...


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

Есть один вариант использования, когда последовательность символов := уже является допустимой в Python. В f-строках используется двоеточие (:), чтобы отделить значения от их спецификации формата. Например:

>>> x = 3
>>> f"{x:=8}"
'       3'


:= В этом случае действительно выглядит как оператор "морж", но эффект совершенно иной. Чтобы интерпретировать x:=8 внутри f-строки, выражение разбивается на три части: x, :, и =8.

Здесь x - это значение, : служит разделителем, а =8 - это спецификация формата. В соответствии с мини-языком спецификации формата Python , в этом контексте = определяет параметр выравнивания. В этом случае значение заполняется пробелами в поле шириной 8.

Чтобы использовать выражения присваивания внутри f-строк, вам необходимо добавить круглые скобки:

>>> x = 3
>>> f"{(x := 8)}"
'8'

>>> x
8


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

Теперь рассмотрим некоторые другие ситуации, в которых выражения присваивания недопустимы:

  • Присвоение атрибутов и элементов: Вы можете присваивать только простые имена, а не имена с пунктиром или индексом:

    >>> (mapping["hearts"] := "♥")
    SyntaxError: cannot use assignment expressions with subscript
    
    >>> (number.answer := 42)
    SyntaxError: cannot use assignment expressions with attribute
    

    Это приводит к сбою с описательным сообщением об ошибке. Простого обходного пути не существует.

  • Повторяемая распаковка: Вы не можете распаковать файл при использовании оператора walrus:

    >>> lat, lon := 59.9, 10.8
    SyntaxError: invalid syntax
    

    Если вы заключите все выражение в круглые скобки, то Python интерпретирует его как кортеж из 3 элементов с тремя элементами lat, 59.9, и 10.8.

  • Расширенное присваивание: Вы не можете использовать оператор walrus в сочетании с операторами расширенного присваивания, такими как +=. В результате возникает проблема SyntaxError:

    >>> count +:= 1
    SyntaxError: invalid syntax
    

    Самым простым решением было бы выполнить расширение явно. Вы могли бы, например, сделать так (count := count + 1). В PEP 577 изначально описывалось, как добавлять расширенные выражения присваивания в Python, но предложение было отозвано.

Когда вы используете оператор walrus, он во многом будет вести себя аналогично традиционным операторам присваивания:

  • Область действия цели назначения такая же, как и для назначений. Это будет соответствовать правилу LEGB. Как правило, присвоение происходит в локальной области видимости, но если целевое имя уже объявлено global или nonlocal, это объявление выполняется.

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

    >>> number = 3
    >>> if square := number ** 2 > 5:
    ...     print(square)
    ...
    True
    

    square привязан ко всему выражению number ** 2 > 5. Другими словами, square получает значение True, а не значение number ** 2, что и было задумано. В этом случае вы можете заключить выражение в круглые скобки:

    >>> number = 3
    >>> if (square := number ** 2) > 5:
    ...     print(square)
    ...
    9
    

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

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

    >>> walrus = 3.7, False
    >>> walrus
    (3.7, False)
    
    >>> (walrus := 3.8, True)
    (3.8, True)
    >>> walrus
    3.8
    
    >>> (walrus := (3.8, True))
    (3.8, True)
    >>> walrus
    (3.8, True)
    

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

  • Рекомендации по стилю для оператора walrus в основном такие же, как и для оператора =, используемого для назначения. Во-первых, всегда добавляйте в свой код пробелы вокруг оператора :=. Во-вторых, при необходимости заключайте выражение в круглые скобки, но избегайте добавления лишних скобок, которые вам не нужны.

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

Подводные камни оператора Walrus

Оператор walrus - это более новый синтаксис, доступный только в Python 3.8 и более поздних версиях. Это означает, что любой написанный вами код, использующий синтаксис :=, будет работать только в этих версиях Python.

Если вам нужна поддержка устаревших версий Python, вы не сможете отправлять код, использующий выражения присваивания. Как вы узнали из этого руководства, вы всегда можете писать код без оператора walrus и оставаться совместимым со старыми версиями.

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

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

Заключение

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

В этом руководстве вы узнали, как:

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

Чтобы узнать больше о деталях выражений присваивания, смотрите PEP 572. Вы также можете ознакомиться с докладом на конференции PyCon 2019 PEP 572: Оператор Walrus, где Дастин Ингрэм рассказывает как об операторе walrus, так и о дискуссии вокруг ВООДУШЕВЛЕНИЕ.

Получите свой код: Нажмите здесь, чтобы загрузить бесплатный пример кода, который показывает, как использовать оператор walrus в Python.

Часто задаваемые вопросы

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

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

Оператор walrus (:=) - это оператор, который позволяет присваивать значения переменным как части выражения.

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

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

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

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

<отметить класс="marker-highlight"> Пройдите тест: Проверьте свои знания с помощью нашего интерактивного теста ”Оператор Walrus: выражения присваивания в Python". По завершении вы получите оценку, которая поможет вам отслеживать прогресс в обучении:

<время работы/> The Walrus Operator: Python's Assignment Expressions

Интерактивная викторина

Оператор Walrus: выражения присваивания в Python

В этом тесте вы проверите свое понимание оператора walrus в Python. Этот оператор был представлен в Python 3.8, и его понимание может помочь вам написать более краткий и эффективный код.

<статус завершения article-slug="python-walrus-operator" class="btn-group mb-0" data-api-article-bookmark-url="/api/v1/articles/python-walrus-operator/bookmark/" data-api-article-завершение-status-url="/api/v1/articles/python-walrus-operator/завершение_статуса/"> <кнопка поделиться bluesky-text="Интересная статья на #Python от @realpython.com :" email-body="Ознакомьтесь с этой статьей о Python:%0A%0 Оператор Walrus: выражения присваивания в Python" email-subject="Статья о Python для вас" twitter-text="Интересная статья о Python" автор @realpython:" url="https://realpython.com/python-walrus-operator /" url-title="Оператор Walrus: выражения присваивания в Python">

Смотрите сейчас, к этому уроку прилагается соответствующий видеокурс, созданный командой Real Python. Посмотрите его вместе с письменным руководством, чтобы углубить свое понимание: Выражения присваивания в Python и использование оператора Walrus

Back to Top