Когда и зачем использовать оператор := в Python

Совсем недавно Python 3.8 представил оператор присвоения с двоеточием :=, аналогичный оператору присвоения =. Использование этого оператора позволяет ускорить и сократить код.

Эта нотация берёт своё начало в математике. При записи уравнений можно написать что-то вроде a=5, a+b=7. Тогда, используя простую алгебраическую операцию, легко вычислить, что b=2. В этом контексте знак равенства означает тождество. Переменные a и b являются постоянными числами, и, хотя их значение неизвестно при инициализации задачи, они существуют и не изменяются.

С другой стороны, в математике существует другая нотация для обозначения отношения ‘x определяется как y’. Запись x := y не означает, что x и y равны друг другу. Здесь x определён как любое значение y. Уравнение скорее одностороннее, чем симметричное, что несколько сложно понять. Эта нотация применяется только для длинных списков определений переменных в узкоспециализированных научных статьях.

Однако в самой последней версии Python 3.8 стало общепринятым использование := или оператора “морж” (он действительно похож на голову лежащего моржа). С его помощью можно определить переменную в границах выражения в контексте программирования.

Так разработчики Python обосновали введение оператора “морж”:

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

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

Рассмотрим следующий код с функцией f, определённой как f = lambda x : x+2, которая просто прибавляет 2 к любым введённым данным:

data = [1,2,3,4]
f_data = [y for x in data if (y := f(x)) is not 4]

Получим [3, 5, 6], поскольку эти результаты функции не равны 4. Это значительно более эффективная реализация, чем её альтернатива, дважды прогоняющая введённые данные через функцию:

f_data = [f(x) for x in data if f(x) is not 4]

Будьте осторожны: поскольку версия 3.8 является совсем новой, некоторые не обновлённые среды могут не поддерживать её.

Давайте рассмотрим другой пример. Есть некий текстовый файл text.txt. Он содержит три строки с буквами от a до i.

abc
def
ghi

Допустим, мы хотим пройти циклом по файлу строка за строкой. Существует несколько способов сделать это. Например, использовать встроенную функцию .readlines().

for line in open('text.txt','r').readlines():
    print(line)

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

for line in open('text.txt','r').read().split('\n'):
    print(line)

Оно тоже работает, но составные функции не настолько просты и понятны, как могли бы быть. Запишем с помощью оператора “морж”:

while chunk := open('text.txt').read():
    print(chunk)

Здесь мы просто определяем chunk как чтение файла. Просто, коротко и чисто. С другой стороны, запись while chunk = open… была бы неверной, потому что нельзя создавать присвоение переменных при вычислении отдельного выражения.

В качестве следующего примера возьмём заданное выше определение функции f(x), добавлявшей двойку к вводу. Следующая конструкция списка g полностью допустима с использованием оператора “морж”:

g = [y := f(3), y**2, y**3] #содержимое: [5, 25, 125]

Здесь y определяется как f(3) и используется в том же выражении. Нельзя написать что-то вроде g = [y=f(3), …], поскольку присвоение переменных с использованием = должно выполняться отдельной строкой, а не внутри другого выражения. С помощью стандартного оператора равенства то же самое записывается в две строки:

y = f(3)
g = [y, y**2, y**3]

Теперь использование оператора морж должно быть довольно ясным.

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

Поскольку присвоение переменной в форме var = expr должно записываться в отдельной строке, оператор “морж” позволяет сократить пространство, запуская присвоение переменной с выполнением внутри другого выражения, будь то создание списка или чтение файла.

Во многих случаях оператор := аналогичен определению переменных-заполнителей, например for i in range(x):, в котором переменная i инициализируется внутри выражения цикла for. Его можно рассматривать как расширение или обобщение этих “инициализаций скрытых переменных”.

Конечно, оператор “морж” нельзя использовать везде. Следующее недопустимо:

a := 3 # должно быть a=3.
a = b := 4 # должно быть a=b=4
a = (b := 4) # допустимо, но не рекомендуется

Давайте разберём, почему работает выражение a=(b:=4). Это происходит потому, что b:=4 не только устанавливает b равным четырём, но и возвращает его значение. Поэтому работает и приведённый выше оператор генератора списков (if y := f(x)) is not 4]). Он вычисляет b:=4 как выражение, возвращающее значение, в итоге и a, и b имеют значение 4.

Со скобками вокруг первого оператора (a := 3) он выполняется корректно, потому что использование скобок помечает всё внутри них как выражение. Операторы “морж” могут использоваться только внутри других выражений.

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

Рассмотрим функцию exponent, которая увеличивает base по exp.

def exponent(base,exp):
    return base**exp

Допустим, нам нужно получить значение четырёх в кубе, сохранив при этом значение степени (3). Воспользуемся оператором “морж”:

exponent(base=4, exp=(storage:=3))

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

Оператор “морж” можно использовать даже в операторах if. В примере ниже (placeholder:=x) вычисляется независимо от значения x. Если выход равен четырём, он всё равно сохраняется в плейсхолдере. Обычно такое использование не имеет особой практической ценности, поскольку выполняется избыточное присваивание (что если x!=4?), но полезно знать, что такое возможно.

x = 4
if (placeholder:=x) == 4:
    print(placeholder) #output: 4

В качестве ещё одного примера кода не в стиле Python, рассмотрим следующую допустимую функцию:

f = lambda x : (m := x+1) + (m**2)

f(3) возвращает 20, потому что (m := x+1) оценивается как 4 и (m**2) оценивается как 16. Их сумма равна 20, это вычисляется довольной чистым, но при этом несколько хитрым методом.

Как бы то ни было, оператор “морж”  —  это весьма полезная функция.

Ключевые моменты

  • Оператор “морж” записывается так := и представлен в Python 3.8.
  • Этот оператор используется только для присваивания переменных внутри других выражений. По крайней мере он помогает сэкономить несколько строк кода, а в некоторых случаях способен значительно ускорить обработку больших данных.
  • Операторы “морж” можно использовать везде  —  от циклов до функций генераторов списка или операторов if для обходного присваивания переменных.

Читайте также:

Читайте также:

Читайте нас в Telegram, VK и Яндекс.Дзен


Перевод статьи Andre Ye: When and Why to Use := Over = in Python

Предыдущая статьяОтображение превью камеры с помощью PreviewView
Следующая статьяПочему я перешёл на Linux после 10 лет работы на Windows