Моржовый оператор := в Python

Обновлено:

Начиная с Python 3.8 в языке появился новый оператор:

:=

Его официальное название:

assignment expression

то есть выражение присваивания.

Также его часто называют:

walrus operator

или по-русски:

моржовый оператор

Название появилось из-за внешнего вида:

:=

который напоминает морду моржа: : можно представить как глаза, а = — как клыки.

Оператор был добавлен в Python в рамках PEP 572.


1. Обычное присваивание =

До этого переменную мы создавали обычным присваиванием:

text = 'Python'

print(text)

Сначала выполняется:

text = 'Python'

и только потом переменную можно использовать.


2. = является не выражением, а отдельной инструкцией присваивания

Например, так написать нельзя:

if text = input('Введите текст: '):
    print(text)

Получим:

SyntaxError

Потому что после:

if

Python ожидает выражение, результат которого можно проверить.

Обычное:

text = ...

таким выражением не является.


3. Моржовый оператор решает эту задачу

Оператор:

:=

позволяет:

  1. вычислить выражение;
  2. сохранить результат в переменную;
  3. сразу же вернуть это значение как результат самого выражения.

Например:

print(
    length := len('Python')
)

Результат:

6

При этом переменная:

length

тоже была создана:

print(length)

Результат:

6

4. Как работает :=

В выражении:

length := len('Python')

сначала выполняется:

len('Python')

Получаем:

6

Затем:

length

начинает ссылаться на:

6

И одновременно всё выражение:

length := len('Python')

тоже даёт значение:

6

Поэтому его можно сразу передать в print().


Обычный вариант и :=

5. Без моржового оператора

length = len('Python')

print(length)

6. С моржовым оператором

print(
    length := len('Python')
)

Здесь создание переменной и использование результата объединились в одно выражение.


Использование внутри if

7. Получаем пользовательский ввод

Без :=:

user_input = input(
    'Введите текст: '
).strip()

if user_input:
    print(
        f'Вы ввели: {user_input}'
    )
else:
    print('Строка пустая')

С моржовым оператором:

if (
    user_input := input(
        'Введите текст: '
    ).strip()
):
    print(
        f'Вы ввели: {user_input}'
    )
else:
    print('Строка пустая')

8. Что здесь происходит

Сначала:

input(
    'Введите текст: '
).strip()

возвращает строку.

Затем эта строка сохраняется:

user_input := ...

После этого полученная строка сразу используется как условие:

if ...

Если строка непустая:

True

выполняется первая ветка.

Если пользователь ничего не ввёл:

''

пустая строка преобразуется в:

False

и выполняется else.


Присваивание и дополнительная проверка

9. Проверяем чётность числа

Можно не просто создать переменную, но и сразу использовать её в более крупном выражении:

if (
    number := int(
        input('Введите число: ')
    )
) % 2 == 0:
    print(
        f'{number} — чётное число'
    )
else:
    print(
        f'{number} — нечётное число'
    )

Например, пользователь вводит:

12

Сначала выполняется:

int(
    input(...)
)

Получаем:

12

Дальше:

number := 12

Теперь:

number

ссылается на 12.

А условие продолжает вычисляться:

12 % 2 == 0

Получаем:

True

10. Проверка строки

Например:

if (
    text := input(
        'Введите строку: '
    ).strip().lower()
).startswith('python'):
    print(
        f'Подходящая строка: {text}'
    )
else:
    print('Строка не подходит')

Здесь результат цепочки:

input(...)
.strip()
.lower()

одновременно:

  • сохраняется в text;
  • используется для проверки через startswith().

Где особенно полезен :=

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

  1. проверить;
  2. а затем использовать повторно.

Без := приходится сначала отдельно создавать переменную.

С := иногда эти действия можно объединить.


while и моржовый оператор

11. Ввод чисел без :=

Предположим, пользователь вводит числа.

Число:

0

означает окончание ввода.

Один из вариантов:

total = 0

while True:
    number = int(
        input(
            'Введите число, '
            '0 — закончить: '
        )
    )

    if number == 0:
        break

    total += number

print(total)

12. Вариант с :=

Можно написать:

total = 0

while (
    number := int(
        input(
            'Введите число, '
            '0 — закончить: '
        )
    )
) != 0:
    total += number

print(total)

Здесь конструкция:

number := int(input(...))

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

number

После этого проверяется:

number != 0

Если:

True

выполняется тело цикла.

Если пользователь ввёл:

0

получаем:

0 != 0

то есть:

False

и цикл заканчивается.


13. Ещё более короткая проверка

Так как:

bool(0)

возвращает:

False

а любое другое целое число обычно:

True

можно написать:

total = 0

while number := int(
    input(
        'Введите число, '
        '0 — закончить: '
    )
):
    total += number

print(total)

Но вариант:

while (
    number := int(input(...))
) != 0:

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


while со строками

14. Ввод строк до пустой строки

words = []

while word := input(
    'Введите слово: '
).strip():
    words.append(word)

print(words)

Например:

Введите слово: Python
Введите слово: SQL
Введите слово: Django
Введите слово:

После пустого ввода цикл заканчивается.

Получим:

['Python', 'SQL', 'Django']

Здесь := особенно удобно: значение одновременно сохраняется и проверяется.


:= внутри list comprehension

15. Накопительная сумма

Возьмём:

numbers = [
    1,
    2,
    3,
    4,
    5,
    6,
    7,
    8,
]

Создадим:

total = 0

result = [
    total := total + number
    for number in numbers
]

print(result)

Результат:

[1, 3, 6, 10, 15, 21, 28, 36]

16. Как работает этот алгоритм

Изначально:

total = 0

Первый элемент:

number = 1

Выполняется:

total := total + number

То есть:

0 + 1 = 1

Теперь:

total = 1

и в новый список добавляется:

1

Следующая итерация:

total = 1
number = 2

Получаем:

1 + 2 = 3

В список добавляется:

3

Дальше:

3 + 3 = 6
6 + 4 = 10
10 + 5 = 15
...

И получаем накопительные суммы:

[1, 3, 6, 10, 15, 21, 28, 36]

17. total остаётся доступен

После:

result = [
    total := total + number
    for number in numbers
]

можно проверить:

print(total)

Результат:

36

Это важная особенность assignment expression внутри comprehension: имя, которому присваивается значение через :=, связывается во внешней для comprehension области видимости.


Ещё один пример с list comprehension

18. Вычисляем длину строки только один раз

words = [
    'cat',
    'python',
    'sql',
    'javascript',
    'code',
]

result = [
    (
        word,
        length,
    )
    for word in words
    if (
        length := len(word)
    ) >= 5
]

print(result)

Получим:

[
    ('python', 6),
    ('javascript', 10),
]

Здесь:

length := len(word)

одновременно:

  • вычисляет длину;
  • сохраняет её;
  • позволяет использовать её в условии;
  • позволяет затем положить её в результат.

Без := пришлось бы либо вычислять:

len(word)

несколько раз, либо переписывать алгоритм обычным циклом.


Когда := действительно полезен

19. Убираем повторное вычисление

Представим:

text = 'Python backend developer'

Без :=:

words = text.split()

if len(words) >= 3:
    print(words)

Здесь код и так хороший.

Но если значение нужно получить непосредственно внутри условия и потом использовать:

if (
    words := text.split()
) and len(words) >= 3:
    print(words)

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


Когда := использовать не нужно

20. Не сокращаем код любой ценой

Можно написать:

print(
    number := 100
)

Но если никакой пользы от объединения действий нет:

number = 100

print(number)

обычно читается проще.

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

=

во всей программе.

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


= и :=

21. Главное различие

Обычное присваивание:

number = 10

создаёт или переназначает переменную.

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


Моржовый оператор:

number := 10

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

10

Поэтому его можно использовать внутри других выражений.

Например:

if (number := 10) > 5:
    print(number)

Скобки

22. Скобки часто делают код понятнее

Например:

if (
    number := int(
        input('Введите число: ')
    )
) > 0:
    print(number)

Скобки позволяют хорошо видеть:

что именно присваивается

и:

что затем проверяется

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


Нельзя путать := и именованный аргумент

Например:

print(
    value=10
)

здесь:

value=10

не означает создание переменной.

Так записываются именованные аргументы функции.

Если функция не имеет такого параметра, может возникнуть:

TypeError

Это совершенно другая конструкция.

Assignment expression выглядит именно так:

value := 10

Главное по теме

Моржовый оператор:

:=

появился в:

Python 3.8

и был добавлен через:

PEP 572

Его официальное название:

assignment expression

Он позволяет одновременно:

вычислить значение
↓
сохранить его в переменную
↓
сразу использовать это значение

Например:

if (
    text := input(
        'Введите текст: '
    ).strip()
):
    print(text)

Особенно удобно использовать :=:

if ...
while ...

в некоторых:

list comprehensions

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


Моржовый оператор не заменяет обычное присваивание:

=

Если обычная запись:

value = expression

понятнее, сокращать её не нужно.

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


Задачи

Задача 1. Получение списка слов

Напишите функцию:

get_words_if_enough()

Функция принимает:

  • строку text;
  • минимальное количество слов min_count.

Необходимо:

  1. разделить строку на слова с помощью split();
  2. использовать моржовый оператор при получении списка слов;
  3. если количество слов не меньше min_count, вернуть список слов;
  4. иначе вернуть пустой список.

Примеры:

print(
    get_words_if_enough(
        'Python SQL Django',
        3,
    )
)
# ['Python', 'SQL', 'Django']
print(
    get_words_if_enough(
        'Python SQL',
        3,
    )
)
# []
print(
    get_words_if_enough(
        'Python backend developer',
        2,
    )
)
# ['Python', 'backend', 'developer']

Задача 2. Накопительные суммы

Напишите функцию:

get_running_totals()

Функция принимает список чисел и возвращает новый список накопительных сумм.

Необходимо использовать:

  • list comprehension;
  • моржовый оператор.

Например:

[1, 2, 3, 4]

должно превратиться в:

[1, 3, 6, 10]

Потому что:

1
1 + 2 = 3
1 + 2 + 3 = 6
1 + 2 + 3 + 4 = 10

Тесты:

print(
    get_running_totals(
        [1, 2, 3, 4]
    )
)
# [1, 3, 6, 10]
print(
    get_running_totals(
        [10, 20, 30]
    )
)
# [10, 30, 60]
print(
    get_running_totals(
        [5]
    )
)
# [5]

Задача 3. Деление числа пополам

Напишите функцию:

get_halves()

Функция принимает положительное целое число.

Необходимо с помощью:

while

и моржового оператора постоянно выполнять целочисленное деление числа на 2.

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

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

Например:

20
↓
10
↓
5
↓
2
↓
1
↓
0

Функция должна вернуть:

[10, 5, 2, 1]

Тесты:

print(
    get_halves(20)
)
# [10, 5, 2, 1]
print(
    get_halves(8)
)
# [4, 2, 1]
print(
    get_halves(1)
)
# []