Python 3.8 Добавляет Оператор Моржа
Последняя версия Python, 3.8, доступна со многими новыми функциями и оптимизациями. Заметные улучшения включают в себя оператор моржа и позиционные параметры.
Одним из основных изменений в Python 3.8 является добавление так называемого оператора «морж», который добавляет поддержку выражений назначения в Python. Оператор “морж”: = означает, что вы можете присваивать значения переменной как части выражения, поэтому сохраните некоторые строки кода, когда захотите использовать значение выражения позже в своей программе.
Также добавлена поддержка только позиционных параметров (/). Многие встроенные и стандартные библиотечные функции CPython принимают только позиционные параметры, и эта новая поддержка облегчает работу с такими функциями.
Протокол “vectorcall” был добавлен в API Python/C. Это основано на соглашении «fastcall», которое уже используется внутри CPython. Новые функции могут использоваться любым пользовательским классом расширения. Большая часть нового API является закрытой в CPython 3.8, и разработчики планируют доработать семантику и сделать ее общедоступной в Python 3.9. Новый vectorcall применяется только к API Python/C, он не влияет на язык Python или стандартную библиотеку.
Текущая версия, 3.8. добавляет крюк аудита и Проверенный открытый крюк. Оба они могут использоваться из Python или собственного кода и предназначены для того, чтобы приложения и фреймворки могли использовать дополнительные уведомления, а также позволяли встраивателям или системным администраторам развертывать сборки Python, где аудит всегда включен.
Python 3.8.0 доступен на странице загрузки Python, и для разработчиков, еще не готовых перейти к последнему выпуску, также доступен Python 3.7.5. Согласно Python Insider, выпуски исправлений ошибок 3.7 x будут продолжаться до середины 2020 года, а исправления безопасности-до середины 2023 года.
Майк Джеймс-автор книги Programmer’s Python: Everything-это объект, опубликованный издательством I/O Press как часть библиотеки I Programmer.
Моржовый оператор в Python
Приглашаем также всех желающих на двухдневный онлайн-интенсив «Разработка десктоп-приложения с помощью библиотеки Tkinter». На интенсиве получим начальные навыки бэкенд-разработки на Python, а также начнем разработку десктоп-приложения с помощью библиотеки Tkinter.

Моржовый (walrus) оператор, появившийся в Python 3.8, дает возможность решить сразу две задачи: присвоить значение переменной и вернуть это значение, поэтому порой можно написать код короче и сделать его более читаемым, и он может быть даже более эффективным с точки зрения вычислений.
Давайте посмотрим на моржовый оператор и приведем примеры того, где он может быть полезен.
Простой оператор присваивания
Все мы знаем, как присвоить значение переменной. Делается это с помощью простого оператора присваивания:
num = 15
И если мы хотим вывести значение этой переменной с помощью функции print , то передать переменную num мы можем следующим образом:
print(num) # 15
Моржовый оператор
Добавленный в Python 3.8 моржовый оператор (:=), формально известен как оператор присваивания выражения. Он дает возможность присвоить переменные в выражении, включая переменные, которых еще не существует. Как было сказано выше, с помощью простого оператора присваивания (=) мы назначили num равным 15 в контексте отдельного оператора.
Выражение вычисляется как значение. А инструкция выполняет какое-то действие.
Другими словами, моржовый оператор позволяет нам как присваивать значение переменной, так и возвращать это значение в одном и том же выражении. А называется он так, потому что символ (:=) похож на глаза и бивни моржа, лежащего на боку.

Expr вычисляется, а затем присваивается имени переменной. Это значение и будет возвращено. Давайте рассмотрим некоторые примеры использования этого оператора.
Простой пример
Лучший способ понять, как работает моржовый оператор – этот простой пример. Как и выше, мы хотим присвоить 15 переменной num , а затем вывести значение num . Мы можем выполнить обе эти задачи в одной строке кода, используя новый оператор следующим образом:
print(num := 15) # 15
Значение 15 присваивается num . Затем возвращается то же значение, которое становится аргументом для функции print . Таким образом, выводится 15.
Если мы попытаемся сделать то же самое с помощью обычного оператора присваивания, то получим ошибку типа, поскольку num = 15 ничего не возвращает.
print(num = 15) # TypeError
Другой пример
Допустим, мы хотим продолжить запрашивать какие-то данные у пользователя. Если пользователь ничего не вводит, мы можем перестать запрашивать дополнительный ввод. Сделать это с помощью цикла while можно следующим образом:

Мы просим пользователя ввести что-нибудь и присваиваем вводу значение. Затем создаем цикл while , который выполняется до тех пор, пока введенное значение не пустая строка. Мы выводим «Nice!», если пользователь успешно ввел что-то. Зачем мы запрашиваем у пользователя следующий ввод, присваиваем ему значение и перезапускаем процесс.
Давайте попробуем сделать это с помощью моржового оператора:

Мы запрашиваем у пользователя входные данные и присваиваем их с помощью моржового оператора. Значение это позже возвращается и сравнивается с пустой строкой. Если в результате сравнения приходит True (то есть не равно пустой строке), код в цикле while выполняется и выводится «Nice!». Если приходит False , то дальнейший код не выполняется.
Пример со списочным выражением
Еще одним примером того, как можно улучшить читаемость кода и его вычислительную эффективность, служит списочное выражение, которое фильтрует значения.
Допустим, у нас есть список чисел num_list , и мы хотим добавить в список куб числа только в случае, если это значение меньше 20. Сделать это можно следующим образом:

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

Значение cube(x) присваивается y , затем возвращается и сравнивается с 20. Значение y будет добавлено в список только в том случае, если оно меньше 20. Обратите внимание, что функция cube() вызывается только один раз, что повышает эффективность кода. И повышение эффективности тем существеннее, чем более сложна для вычисления функция.
Надеюсь, это руководство по использованию моржового оператора в Python было вам полезно. Спасибо за прочтение!
Узнать подробнее о курсе «Python Developer. Basic«.
Регистрация на двухдневный онлайн-интенсив «Разработка десктоп-приложения с помощью библиотеки Tkinter»: день 1, день 2.
- Блог компании OTUS
- Python
- Программирование
Вот почему нужно использовать оператор := в Python

Сегодня рассказываем о самом странном операторе Python — операторе моржа. Для чего он нужен, и как использовать его с учётом других особенностей языка? Подробности к старту курса по Fullstack-разработке на Python — под катом:
- Основы
- Накапливание (аккумулирование) данных на месте
- Именование значений внутри f-строки
- Any и All
- Подводные камни и ограничения
- Заключительные мысли
Основы
Посмотрим на основные варианты применения оператора моржа, которые могут убедить вас попробовать, если вы ещё не знакомы с := .
В первом примере, который я хочу показать, оператор моржа сокращает количество вызовов функций.
Представим функцию func() , которая выполняет очень ресурсоёмкие вычисления. Её работа занимает много времени, поэтому вызывать функцию много раз не хочется:
# "func" вызывается три раза result = [func(x), func(x)**2, func(x)**3] # Используем результат "func" в одну строку, без многострочного кода result = [y := func(x), y**2, y**3]
В объявлении первого списка выше func(x) вызывается трижды. Каждый раз возвращается один и тот же результат — и это пустая трата времени ресурсов. При перезаписи с оператором моржа func() вызывается только один раз: её результат присваивается y и повторно используется для оставшихся значений списка. Вы можете возразить: «Морж не нужен, я могу просто добавить y = func(x) перед объявлением списка!» Да, но это лишняя строка кода, и на первый взгляд — не зная, что func(x) работает очень медленно, — может быть непонятно, для чего нужна переменная y .
Если это не убедило вас, есть ещё кое-что. Вот списковое включение с той же дорогостоящей func() :
result = [func(x) for x in data if func(x)] result = [y for x in data if (y := func(x))]
В первой строке функция func(x) в каждом цикле вызывается дважды. С оператором моржа функция вычисляется один раз, внутри if , а затем результат используется повторно. Длина кода одинаковая, обе строки одинаково читаемы, но вторая в два раза эффективнее. Производительность можно сохранить, заменив оператор моржа на полный цикл for , но для этого потребуется 5 строк кода.
Один из самых распространённых вариантов применения оператора моржа — сокращение вложенных условий, например в сопоставлении при работе с регулярными выражениями:
import re test = "Something to match" pattern1 = r"^.*(thing).*" pattern2 = r"^.*(not present).*" m = re.match(pattern1, test) if m: print(f"Matched the 1st pattern: ") else: m = re.match(pattern2, test) if m: print(f"Matched the 2nd pattern: ") # --------------------- # Чище if m := (re.match(pattern1, test)): print(f"Matched 1st pattern: ''") elif m := (re.match(pattern2, test)): print(f"Matched 2nd pattern: ''")
Код сократился с 7 до 4 строк, а за счёт удаления вложенного if стал более читаемым.
Следующая в списке — идиома «loop-and-half» («цикл пополам»):
while True: # Цикл command = input("> ") if command == 'exit': # и пополам break print("Your command was:", command) # --------------------- # Чище while (command := input("> ")) != "exit": print("Your command was:", command)
Обычное решение — фиктивный бесконечный цикл while , в котором поток управления передаётся оператору break . Но также можно задействовать оператор моржа, чтобы переназначить значение command , а затем использовать его в условном цикле while в той же строке. Это сделает код намного чище и короче.
Аналогичное упрощение применимо и к другим циклам while , например при чтении файлов построчно или при получении данных из сокета.
Накапливание (аккумулирование) данных на месте
Перейдём к более сложным случаям применения оператора моржа :
data = [5, 4, 3, 2] c = 0; print([(c := c + x) for x in data]) # c = 14 # [5, 9, 12, 14] from itertools import accumulate print(list(accumulate(data))) # --------------------- data = [5, 4, 3, 2] print(list(accumulate(data, lambda a, b: a*b))) # [5, 20, 60, 120] a = 1; print([(a := a*b) for b in data]) # [5, 20, 60, 120]
Первые две строки показывают, как использовать := для вычисления промежуточного результата. В настолько простом случае лучше подойдёт функция itertools , например accumulate , как в двух следующих строках. Но в ситуациях сложнее itertools довольно быстро становится нечитаемым, и, на мой взгляд, версия с := намного лучше, чем с lambda .
Если вы всё ещё не уверены, ознакомьтесь с нечитабельными примерами в документации accumulate (с накоплением процентов или логистическим отображением). Попробуйте переписать их с оператором моржа, они станут выглядеть намного лучше.

Именно так, шаг за шагом, вы станете востребованным профессионалом в области IT:
- Профессия Fullstack-разработчик на Python (15 месяцев)
- Профессия Data Scientist (24 месяца)
Именование значений внутри f-строки
Этот пример показывает возможности и ограничения := , но не лучшие практики.
Если очень хочется, := можно использовать внутри f-строк:
from datetime import datetime print(f"Today is: , which is ") # Today is: 2022-07-01, which is Friday from math import radians, sin, cos angle = 60 print(f'\N <(theta := radians(angle)) =: .2f>, , ') # angle=60° (theta := radians(angle)) = 1.05, sin(theta) = 0.87, cos(theta) = 0.50
В первом print оператор := определяет переменную today , которая затем используется в той же строке; today избавляет нас от повторного вызова datetime.today() .
Точно так же во втором примере объявляется переменная theta , которая повторно используется для вычисления sin(theta) и cos(theta) . В этом случае мы также используем его в сочетании с оператором «обратный морж» — на самом деле это просто = , с ним выражение выводится рядом с его значением, а : используется для форматирования выражения.
Обратите внимание: чтобы f-строка правильно интерпретировала выражения с оператором моржа, их нужно заключать в круглые скобки.
Any и All
Чтобы проверить, удовлетворяют ли какие-то значения в некотором итерируемом объекте определённому условию, можно восопользоваться функциями any() и all() . Но что делать, если нужно зафиксировать значение, из-за которого any() вернуло True (так называемое «свидетельство»), или значение, которое привело к сбою all() — «контрпример»?
numbers = [1, 4, 6, 2, 12, 4, 15] # Возвращает только логические значения, а не обычные print(any(number > 10 for number in numbers)) # True print(all(number < 10 for number in numbers)) # False # --------------------- any((value := number) >10 for number in numbers) # True print(value) # 12 all((counter_example := number) < 10 for number in numbers) # False print(counter_example) # 12
И any() , и all() для вычисления выражения используют вычисление по короткой схеме, то есть прекращают вычисления, как только находят первое «свидетельство» или «контрпример» соответственно. А значит, при таком трюке созданная оператором моржа переменная всегда будет возвращать первого «свидетеля» или «контрпример».
Подводные камни и ограничения
Хотя выше я пытался убедить использовать оператор моржа, думаю, также важно предупредить о некоторых его недостатках и ограничениях. Ниже — подводные камни, с которыми вы можете столкнуться.
В предыдущем примере вы видели, что вычисление по короткой схеме полезно для захвата значений в any() / all() , но иногда это может привести к неожиданным результатам:
for i in range(1, 100): if (two := i % 2 == 0) and (three := i % 3 == 0): print(f" is divisible by 6.") elif two: print(f" is divisible by 2.") elif three: print(f" is divisible by 3.") # NameError: name 'three' is not defined [имя 'three' не определено]
Мы создали условие с двумя соединёнными and присваиваниями. Эти and проверяют, делится ли число на 2, 3 или 6 в зависимости от того, выполняются ли первое, второе или оба условия. На первый взгляд это может показаться хорошим приёмом, но из-за вычисления по короткой схеме, если выражение (two: = i % 2 == 0) не выполняется, вторая часть будет пропущена, а значит, имя three окажется неопределённым или будет иметь устаревшее значение из предыдущего цикла.
Вычисление по короткой схеме может быть и преднамеренным. Использовать его можно с регулярными выражениями, чтобы найти в строке несколько паттернов:
import re tests = ["Something to match", "Second one is present"] pattern1 = r"^.*(thing).*" pattern2 = r"^.*(present).*" for test in tests: m = re.match(pattern1, test) if m: print(f"Matched the 1st pattern: ") else: m = re.match(pattern2, test) if m: print(f"Matched the 2nd pattern: ") # Сопоставлено с первым паттерном: thing # Сопоставлено со вторым паттерном: present for test in tests: if m := (re.match(pattern1, test) or re.match(pattern2, test)): print(f"Matched: ''") # Сопоставлено: 'thing' # Сопоставлено: 'present'
Мы уже видели версию этого фрагмента в первом разделе статьи, где в сочетании с оператором моржа использовались if / elif . Здесь код упрощается ещё больше: условное выражение сокращается до единственного if .
Если вы только знакомитесь с оператором моржа, то заметите, что он заставляет переменные области видимости вести себя по-разному при списковом включении:
values = [3, 5, 2, 6, 12, 7, 15] tmp = "unmodified" dummy = [tmp for tmp in values] print(tmp) # Как и ожидалось, "tmp" не разбился на элементы. Он по-прежему "немодифицированный" total = 0 partial_sums = [total := total + v for v in values] print(total) # Выводит: 50
При обычном списковом включении list , dict или set переменная цикла не просачивается в окружающую область, и поэтому любые существующие переменные с тем же именем не изменятся. Но с оператором моржа переменная из включения ( total в приведённом выше коде) останется доступной после завершения включения, она примет значение из внутреннего включения.
Когда вы освоитесь с оператором моржа, то сможете попробовать его в других случаях. Но вот одно место, где вы никогда не должны его использовать, — это оператор with :
class ContextManager: def __enter__(self): print("Entering the context. ") def __exit__(self, exc_type, exc_val, exc_tb): print("Leaving the context. ") with ContextManager() as context: print(context) # None with (context := ContextManager()): print(context) #
С обычным синтаксисом with ContextManager() as context: . context привязывается к возвращаемому значению context.__enter__() , а если вы используете версию с := — к результату ContextManager() . Часто это не имеет большого значения, ведь context.__enter__() обычно возвращает self , но если это не так, то при отладке возникнут большие проблемы.
Вот что происходит, когда вы используете оператор моржа с менеджером контекста closing :
from contextlib import closing from urllib.request import urlopen with closing(urlopen('https://www.python.org')) as page: for line in page: print(line) # Вывод HTML-кода страницы with (page := closing(urlopen('https://www.python.org'))): for line in page: print(line) # TypeError: 'closing' object is not iterable [объект 'closing' не итерируемый]
Ещё одна проблема, с которой вы можете столкнуться, — приоритет оператора моржа, ведь он ниже приоритета логических операторов:
text = "Something to match." flag = True if match := re.match(r"^.*(thing).*", text) and flag: print(match.groups()) # AttributeError: 'bool' object has no attribute 'group' [у объекта 'bool' нет атрибута 'group'] if (match := re.match(r"^.*(thing).*", text)) and flag: print(match.groups()) # ('thing',)
Здесь мы видим, что присваивание нужно заключить в круглые скобки, чтобы гарантировать, что результат re.match(. ) присваивается переменной. Если этого не сделать, выражение and будет вычисляться первым, то есть будет присвоен логический результат.
И, наконец, не ловушка, а скорее небольшое ограничение. Сейчас с оператором моржа нельзя использовать подсказки встроенного типа. Поэтому, если захочется указать тип переменной, нужно разбить её на 2 строки:
from typing import Optional value: Optional[int] = None while value := some_func(): . # Что-то делаем
Заключительные мысли
Оператором моржа, как и любой другой особенностью синтаксиса, можно злоупотребить: снизить ясность и читабельность кода. Не нужно писать его везде, где только возможно. Относитесь к этому оператору как к инструменту — знайте его преимущества и недостатки, а используйте там, где это уместно.
Если хочется увидеть более практичное и эффективное использование оператора моржа, узнайте, как он появился в стандартной библиотеке CPython — все эти изменения можно найти в этом PR. И рекомендую прочитать PEP 572, в котором есть обоснование появления оператора и ещё больше примеров.

А мы поможем прокачать ваши навыки или с самого начала освоить профессию, востребованную в любое время:
- Профессия Data Scientist (24 месяца)
- Профессия Data Analyst (18 месяцев)
- Профессия Fullstack-разработчик на Python (15 месяцев)
- skillfactory
- программирование
- python
- операторы
- морж
- трюки
- приёмы программирования
- языки программирования
- особенности
- синтаксис
- Блог компании Skillfactory
- Python
- Программирование
Моржовый оператор := в Python


Оператор := был назван моржовым за визуальное сходство с глазами и бивнями моржа, лежащего на боку. (Это не шутка! ;-)). Моржовый оператор появился в версии Python 3.8, но до сих пор ведутся споры о его целесообразности. Так или иначе, «если звезды зажигают — значит — это кому-нибудь нужно?». Поэтому познакомимся с моржовым оператором поближе и рассмотрим, в каких случаях применение := может быть полезным.
Зачем нужен моржовый оператор?
Оператор := — это оператор присваивания, который отличается от привычного нам оператора = тем, что возвращает присвоенное значение. Чтобы понять, как этот оператор работает, и в каких случаях может быть полезным, рассмотрим примеры:
Примеры использования моржового оператора
Пример №1. Присваиваем значение переменной и выводим его на экран:
print( a: = 5 )
Вывод на экран: 5
Обратите внимание, что оператор := работает только в Python, начиная с версии 3.8!
Если мы заменим моржовый оператор на обычный оператор присваивания «=», то получим сообщение об ошибке:
print(a = 5)
Вывод на экран: TypeError: 'a' is an invalid keyword argument for print()
Пример №2. Используем моржовый оператор := в Python в условных выражениях:
Создадим блок с условием, который будет выводить значение переменной a , если оно больше 5. Использование моржового оператора позволяет нам инициализировать переменную a прямо в блоке if :
# с оператором ":=" if (a:=5) > 0: print(a)
Вывод на экран: 5
Для сравнения создадим блок if с аналогичным функционалом, но без использования моржового оператора. Для этого нам придется добавить строку с инициализацией переменной a перед условным блоком:
# без оператора ":=" a = 5 if(a > 0): print(a)
Вывод на экран 5
Пример №3. Использование моржового оператора в генераторах списка:
В приведенном ниже примере будем генерировать список res из значений, превышающих 1, и полученных при целочисленном делении на 2 элементов списка lst :
# С оператором ":=" lst = [0, 2, 4] res = [d for i in lst if (d := i//2) > 1] print(res)
Полученный результат: [2]
На заметку: оператор // — это оператор целочисленного деления. Узнать больше о математических операторах в Python можно по ссылке: «Математические операции в Python».
Без использования моржового оператора нам пришлось бы дважды записывать выражение i//2 , как в приведенном ниже коде:
# без оператора ":=" lst = [0, 2, 4] res = [i//2 for i in lst if i//2 > 1] print(res)
Полученный результат: [2]
Пример №4. Использование оператора « := » при считывании вводимой с клавиатуры информации:
Напишем код считывания введенных с клавиатуры символов. До тех пор, пока пользователь не введет клавишу выхода „q“ введенные пользователем символы будут отображаться на экране. Использование морового оператора в данном случае позволяет сократить код до двух строк:
while (word := input()) != 'q': print(word)
Аналогичный код без использования оператора := уже не так компактен, хоть и прост для понимания:
word = input() while (word != 'q'): print(word) word = input()
![]()
У нас появился Telegram-канал для изучающих Python! Подписывайтесь по ссылке: Кодим на Python! Вместе «питонить» веселее!
Исходя из приведенных выше примеров, можно сделать вывод, что использование моржового оператора позволяет сделать код более лаконичным. Да, это так называемый «синтаксический сахар», конструкция, без которой можно обойтись. Однако, неоспоримое преимущество — более короткий код — превращает оператор := в эффективную конструкцию Python >= 3.8