Инструкции присваивания в Python
- Присваивание в Python и его каноническая форма
- Комбинированные инструкции присваивания
- Позиционное присваивание
- Групповое присваивание одного значения
- Выражения присваивания в Python
- Краткие итоги параграфа
- Вопросы и задания для самоконтроля
Присваивание в Python и его каноническая форма
В Python, как и во многих других языках программирования, каноническая форма операции присваивания имеет вид a = b, где слева от оператора присваивания записывается целевое имя переменной или компонент объекта, а в качестве правого операнда выступает произвольное выражение, которое в результате вычислений дает объект (см. пример №1).
# Присвоили выражение.
a = 5 + 0.3
# Выведет 5.3.
print(a, end='\n\n')
# Присвоили список.
b = [1, 1, 2]
print(b, end='\n\n')
# Изменили первый эл-т списка.
b[0] = 0
# Выведет [0, 1, 2].
print(b, end='\n\n')
# Объявили ф-цию со значением по умолчанию.
def my_func(li=[]):
return li
# Присвоили объект функции.
f = my_func
# Выведет [0, 1, 2].
print(f(b))
# Присвоили вызов функции.
e = my_func(b)
# Выведет [0, 1, 2].
print(e)
5.3
[1, 1, 2]
[0, 1, 2]
[0, 1, 2]
[0, 1, 2]
Пример №1. Каноническая форма присваивания.
Как видим, присваивание действительно выполняется достаточно просто. Однако при этом хотелось бы обратить внимание на следующие особенности присваивания в Python:
- Инструкции присваивания всегда сохраняют ссылки на объекты, например, в переменных или в элементах структур данных, и никогда не создают копии присваиваемых объектов. Как следствие, переменные в Python больше напоминают указатели, чем области хранения данных.
- Объявлять переменные заранее нельзя, т.к. интерпретатор создает переменные в момент присваивания им значений (то есть ссылок на объекты). После инициализации, когда имя переменной будет встречаться в выражениях, оно будет автоматически замещаться объектом, на который ссылается данная переменная. Если же интерпретатор встретит в программе неинициализированную переменную, он возбудит исключение вместо того, чтобы вернуть какое-либо значение по умолчанию (такое поведение призвано облегчить поиск ошибок и опечаток в коде).
- Некоторые инструкции пусть и неявно, но тоже выполняют операцию присваивания. Примерами могут служить импорт модуля, определение функции или класса, указание переменной в цикле for или же передача аргументов функции. В любом случае результатом явных или неявных инструкций присваивания будет связывание имен с ссылками на объекты.
Ну, и в конце пункта еще раз напомним, что в программировании знак равно (=) обычно означает не равенство двух величин, а присваивание значения переменной. Если же нужно указать на равенство двух значений, то используется комбинация символов из двух знаков равно (==).
Комбинированные инструкции присваивания в Python
В дополнение к базовой инструкции присваивания в Python имеется и целый ряд комбинированных инструкций присваивания, которые объединяют операцию присваивания с другой операцией. В общем виде инструкцию присваивания с комбинированным оператором x operator= y можно считать сокращенной записью инструкции x = x operator y. Например, x += y является сокращенной записью инструкции присваивания x = x + y, в которой к значению переменной x прибавляется значение переменной y, а результат присваивается переменной x (см. пример №2).
# Присвоили начальное значение.
a = 3
print('a = 3 ->', a, end='\n\n')
# Теперь a == 9, что равнозначно a = a + 6.
a += 6
print('a += 6 ->', a, end='\n\n')
# Теперь a == 18, что равнозначно a = a*2.
a *= 2
print('a *= 2 ->', a, end='\n\n')
# Теперь a == 9.0, что равнозначно a = a/2.
a /= 2
print('a /= 2 ->', a, end='\n\n')
# Теперь a == 81.0, что равнозначно a = a**2.
a **= 2
print('a **= 2 ->', a, end='\n\n')
# Теперь a == 1.0, что равнозначно a = a%2.
a %= 2
print('a %= 2 ->', a)
a = 3 -> 3
a += 6 -> 9
a *= 2 -> 18
a /= 2 -> 9.0
a **= 2 -> 81.0
a %= 2 -> 1.0
Пример №2. Комбинированная форма присваивания.
Таким образом, комбинированная инструкция присваивания объединяет в себе выражение и присваивание, являясь по сути краткой формой записи кода. И хотя, например, инструкции num += 25 и num = num + 25 дадут один и тот же результат, первая из них выглядит явно компактнее. Кроме того, если объект справа относится к категории изменяемых объектов и поддерживает указанную операцию, комбинированная инструкция присваивания может выполняться даже быстрее за счет непосредственного изменения объекта вместо создания и изменения его копии (см. пример №3).
# Импортируем модуль time.
import time
# Получаем стартовое значение времени.
t_1 = time.time()
# Размер начнет быстро расти, что с
# увеличением кол-ва итераций приведет в
# данном случае к замедлению работы.
li = [0]
# Запускаем цикл 100 тыс. раз.
for i in range(0, 10**5):
# Обычное присваивание.
li = li + [1]
# Получаем конечное значение времени.
t_2 = time.time()
# Вывело 20.3988723754883 сек.
print(round((t_2 - t_1), 13), 'сек', end='\n\n')
# Тоже самое, но для комбинированного присваивания.
# Получаем стартовое значение времени.
t_1 = time.time()
# Здесь список тоже будет расти, но список будет
# изменяться напрямую, а не через создание копии.
li = [0]
# Запускаем цикл 100 тыс. раз.
for i in range(0, 10**5):
# Комбинированное присваивание.
li += [1]
# Получаем конечное значение времени.
t_2 = time.time()
# 0.0080721378326 сек.
print(round((t_2 - t_1), 13), 'сек')
20.3988723754883 сек
0.0080721378326 сек
Пример №3. Преимущества комбинированной формы присваивания.
Метод time() одноименного модуля time стандартной библиотеки Python возвращает время, выраженное в секундах с начала эпохи. В операционных системах Unix, например, за начало эпохи принимается 1 января 1970, 00:00:00 (UTC). Но поскольку в программах в основном используются интервалы времени между двумя событиями, а не время, прошедшее с начала эпохи, знать дату начала эпохи какой-либо конкретной операционной системы вовсе необязательно, т.к. разница между двумя временными точками для всех платформ получается совершенно одинаковой.
Давайте еще раз и в одном месте перечислим основные инструкции присваивания, комбинированные с арифметическими операторами:
- a += b – тоже самое, что и a = a + b;
- a -= b – тоже самое, что и a = a - b;
- a *= b – тоже самое, что и a = a * b;
- a **= b – тоже самое, что и a = a**b;
- a /= b – тоже самое, что и a = a / b;
- a //= b – тоже самое, что и a = a // b;
- a %= b – тоже самое, что и a = a % b.
Позиционное присваивание в Python
Еще одной формой присваивания в Python является позиционное присваивание, которое связывает объекты последовательностей справа от оператора присваивания с именами, перечисляемыми слева от него. При этом присваивание выполняется слева направо согласно местоположениям имен и соответствующих им объектов (см. пример №4).
# Позиционное присваивание кортежа значений кортежу переменных.
a, b = 3, 5
# Выведет a == 3, b == 5.
print('a ==', a, ', b ==', b, end='\n\n')
# Позиционное присваивание списка значений кортежу переменных.
a, b = [3, 5]
# Выведет a == 3, b == 5.
print('a ==', a, ', b ==', b, end='\n\n')
# Позиционное присваивание списка значений списку переменных.
[a, b] = [3, 5]
# Выведет a == 3, b == 5.
print('a ==', a, ', b ==', b, end='\n\n')
# Позиционное присваивание строки списку переменных.
[a, b] = 'AB'
# Выведет a == A, b == B.
print('a ==', a, ', b ==', b, end='\n\n')
# Кол-во присв-ых значений должно совпадать с кол-м переменных.
a, b = 'ABC'
# too many values to unpack (expected 2).
print('a ==', a, ', b ==', b)
a == 3 , b == 5
a == 3 , b == 5
a == 3 , b == 5
a == A , b == B
too many values to unpack (expected 2)
Пример №4. Позиционное присваивание (часть 1).
Как видим, данная форма присваивания позволяет смешивать последовательности разных типов. Здесь главное, чтобы количество присваиваемых значений совпадало с количеством переменных, которым эти значения будут присваиваться. Но и эта проблема может быть решена за счет имеющейся возможности использования в присваивании расширенного синтаксиса распаковывания последовательностей (см. пример №5).
# Используем синтаксис распаковывания последовательности.
a, b, *c = [1, 2, 3, 4, 5]
# Выведет a == 1, b == 2, c == [3, 4, 5].
print('a ==', a, ', b ==', b, ', c ==', c, end='\n\n')
*a, b, c = [1, 2, 3, 4, 5]
# Выведет a == [1, 2, 3], b == 4, c == 5.
print('a ==', a, ', b ==', b, ', c ==', c, end='\n\n')
a, *b, c = [1, 2, 3, 4, 5]
# Выведет a == 1, c == [2, 3, 4], b == 5.
print('a ==', a, ', b ==', b, ', c ==', c, end='\n\n')
# Граничный случай.
a, b, *c = [1, 2, 3]
# Выведет a == 1, b == 2, c == [3].
print('a ==', a, ', b ==', b, ', c ==', c, end='\n\n')
# Еще один граничный случай.
a, b, *c = [1, 2]
# Выведет a == 1, b == 2, c == [].
print('a ==', a, ', b ==', b, ', c ==', c, end='\n\n')
# Еще один граничный случай.
*c, = [1, 2]
# Выведет c == [1, 2].
print('c ==', c, end='\n\n')
# Использовать несколько звездочек запрещено.
# *a, b, *c = [1, 2]
# Так тоже нельзя, нужна последовательность имен.
# *c = [1, 2]
a == 1 , b == 2 , c == [3, 4, 5]
a == [1, 2, 3] , b == 4 , c == 5
a == 1 , b == [2, 3, 4] , c == 5
a == 1 , b == 2 , c == [3]
a == 1 , b == 2 , c == []
c == [1, 2]
Пример №5. Позиционное присваивание (часть 2).
Благодаря наличию имени со звездочкой все лишние значения автоматически помещаются в соответсвующий список именно так, как и ожидается. Однако нужно не забывать, что можно использовать только одну переменную со звездочкой, а сама переменная должна принадлежать последовательности даже в том случае, если она будет одна (поставив запятую в инструкции *c, = [1, 2] мы автоматически получили кортеж и смогли избежать ошибки).
Следует добавить, что расширенная операция распаковывания последовательностей может использоваться не только в случаях явного присваивания значений, но также и в заголовках циклов, вызовах функций и других операциях неявного присваивания значений.
Групповое присваивание одного значения в Python
В случае группового присваивания объект, расположенный справа, присваивается всем переменным группы (см. пример №6). В результате несколько отдельных инструкций присваивания могут быть заменены одной эквивалентной и более компактной инструкцией.
# Используем 3 инструкции присваивания по отдельности.
a = 'Ok'
b = a
c = b
# Выведет a == 'Ok', b == 'Ok', c == 'Ok'.
print('a ==', a, ', b ==', b, ', c ==', c, end='\n\n')
# Или 1 эквивалентную инст-цию группового присваивания.
c = b = a = 'Ok'
# Опять же выведет a == 'Ok', b == 'Ok', c == 'Ok'.
print('a ==', a, ', b ==', b, ', c ==', c)
a == Ok , b == Ok , c == Ok
a == Ok , b == Ok , c == Ok
Пример №6. Присваивание одного значения группе переменных (часть 1).
Следует иметь в виду, что при групповом присваивании в памяти создается всего лишь один объект, разделяемый всеми переменными группы. Поэтому такая форма присваивания будет полезна для неизменяемых объектов, например, чисел или строк. А вот при использовании изменяемых объектов типа списков или словарей нужно быть осторожными, т.к. изменение объекта через одну из переменных группы будет оказывать влияние и на другие переменные (см. пример №7). Это связано с тем, что переменные в Python хранят не сами объекты, а ссылки на них. А раз так, то даже после изменения объекта через одну из ссылок все остальные по-прежнему будут указывать на тот же, пусть и модифицированный, объект.
# Используем число (неизменяемый объект).
c = b = a = 0
# Изменим b и c.
b = 1
c = 2
# Выведет a == 0, b == 1, c == 2.
print('a ==', a, ', b ==', b, ', c ==', c, end='\n\n')
# Используем список (изменяемый объект).
c = b = a = []
# Пробуем изменить b и c.
b += [1]
c += [2]
# Выведет для всех [1, 2].
print('a ==', a, ', b ==', b, ', c ==', c)
a == 0 , b == 1 , c == 2
a == [1, 2] , b == [1, 2] , c == [1, 2]
Пример №7. Присваивание одного значения группе переменных (часть 2).
Таким образом, во избежание проблем с изменяемыми объектами, их инициализацию лучше производить в отдельных инструкциях, как было показано в первой части примера №6.
Выражения присваивания в Python
Все рассмотренные нами формы присваивания относятся к инструкциям. Но их запрещено использовать в выражениях, что иногда может быть весьма полезным. Поэтому начиная с версии Python 3.8 было решено ввести новую конструкцию NAME := expr с возможностью использования в выражениях (см. пример №8). Конструкция получила название выражение присваивания или же именованное выражение, а оператор := стал неофициально называться моржовым оператором.
# В выражении инициализируются сразу 3 переменные.
a = (b := 3) + (c := 5)
# Выведет a == 8, b == 3, c == 5.
print('a ==', a, ', b ==', b, ', c ==', c, end='\n\n')
# Список функций (для однозначности используем скобки).
li = [(x_2 := lambda y: y**2), (x_3 := lambda y: y**3)]
# Выведет li[0](5) == 25, li[1](5) == 125.
print('li[0](5) ==', li[0](5), ', li[1](5) ==', li[1](5))
# Выведет те же результаты: x_2(5) == 25, x_3(5) == 125.
print('x_2(5) ==', x_2(5), ', x_3(5) ==', x_3(5), end='\n\n')
a == 8 , b == 3 , c == 5
li[0](5) == 25 , li[1](5) == 125
x_2(5) == 25 , x_3(5) == 125
Пример №8. Использование выражений присваивания (часть 1).
Использовать выражение присваивания в коде верхнего уровня без скобок запрещается. Например, инструкция x = y := 5 не пройдет, нужно использовать скобки: x = (y := 5). И вообще, использование скобок с моржовым оператором следует сразу же взять на вооружение, т.к. это поможет избежать многих досадных ошибок.
Возможно, кто-то сочтет такое нововведение нецелесообразным, ведь выражения порой и так бывают довольно громоздкими, чтобы вводить туда еще и дополнительные конструкции с набором ограничений. Но ситуации бывают разные как, например, в примере №9.
# Выводим сообщение и призыв к вводу.
print('Введите 2 положительных числа.')
x = float(input('Введите 1-е число: '))
y = float(input('Введите 2-е число: '))
# Для уменьшения объема кода.
mess = 'Разрешены только положительные числа!'
# Осуществляем проверки и вывод результатов.
if y > 0:
res = x/y
if res > 0:
print('x/y =', res)
else:
print(mess, end='\n\n')
else:
print(mess, end='\n\n')
# Выводим сообщение и призыв к вводу.
print('Введите 2 положительных числа.')
x = float(input('Введите 1-е число: '))
y = float(input('Введите 2-е число: '))
# Проверка в одном if.
if y > 0 and (res := x/y) > 0:
# Выводим результат.
print('x/y =', res)
# Иначе сообщение об ошибке.
else:
print('Разрешены только положительные числа!')
Введите 2 положительных числа.
Введите 1-е число: 5
Введите 2-е число: 10
x/y = 0.5
Введите 2 положительных числа.
Введите 1-е число: 5
Введите 2-е число: 10
x/y = 0.5
Пример №9. Использование выражений присваивания (часть 2).
Подробнее о выражениях присваивания можете почитать на официальном сайте в документации к PEP 572 – Assignment Expressions.
Краткие итоги параграфа
- В Python инструкции присваивания всегда сохраняют ссылки на объекты, например, в переменных или в элементах структур данных, и никогда не создают копии присваиваемых объектов.
- Классическая форма присваивания имеет вид NAME = expr, что соответствует синтаксису многих других языков программирования. Однако в Python доступны и другие формы инструкций присваивания: комбинированные инструкции присваивания (например, y **= 3), позиционное присваивание (например, x, y = [3, 5] или x, *y = 'abcdef'), а также групповое присваивание одного значения (например, a = b = c = 33).
- При групповом присваивании одного значения в памяти создается всего лишь один объект, разделяемый всеми переменными группы. Поэтому такая форма присваивания будет полезна для неизменяемых объектов, например, чисел или строк. А вот при использовании изменяемых объектов типа списков или словарей нужно быть осторожными, т.к. изменение объекта через одну из переменных группы будет оказывать влияние и на другие переменные.
- Поскольку в Python запрещается использовать инструкции в выражениях, начиная с версии 3.8 была введена специальная конструкция NAME := expr, получившая название выражения присваивания и предназначенная для присваивания значений внутри выражений, например, a = (b := 3) + (c := 5).
Вопросы и задания для самоконтроля
1. Перечислите основные формы инструкций присваивания в Python. Показать решение.
Ответ. Классическая форма NAME = expr, комбинированные инструкции присваивания NAME operator= expr, позиционное присваивание NAME_1, ..., NAME_n = expr_1, ..., expr_n, а также групповое присваивание одного значения NAME_1 = ... = NAME_n = expr.
2. Имеются две инициализированные переменные a = 'one' и b = 'two'. Поменяйте значения переменных местами, использовав позиционную форму инструкции присваивания. Показать решение.
Ответ. Так как в процессе позиционного присваивания интерпретатор создает временный кортеж, где сохраняются оригинальные значения переменных справа, данная форма присваивания может использоваться для реализации обмена значений переменных без создания временной переменной. Таким образом, кортеж справа автоматически запоминает предыдущие значения переменных.
# Инициализируем переменные.
a = 'one'
b = 'two'
# Производим замену значений.
a, b = 'two', 'one'
print('a == ', a, ', b == ', b, sep='')
a == two, b == one
3. О чем следует помнить, когда трем переменным присваивается один и тот же изменяемый объект? Показать решение.
Ответ. Если, например, выполнить присваивание следующим образом: a = b = c = [], то все три переменные будут ссылаться на один и тот же объект. Поэтому непосредственное изменение объекта с помощью одной переменной (например, A.append(5)) отразится и на других. Однако это справедливо только для изменений, производимых непосредственно в изменяемых объектах, таких как списки или словари. Для неизменяемых объектов, вроде чисел и строк, такой проблемы не возникает.
4. Какие из представленных фрагментов кода содержат ошибки: a, b = [3, '3']; x, *y = 1, 2, 3; *x, = 1, 2, 3; *x, y, *z = 1, 2, 3, 4? Объясните ответ. Показать решение.
Ответ. Все фрагменты кода, кроме последнего, верны. А вот последняя инструкция содержит две переменные со звездочкой, что расширенным синтаксисом распаковывания последовательностей не допускается.
5. Дополнительные тесты по теме расположены в разделе «Инструкции присваивания» нашего сборника тестов.
Быстрый переход к другим страницам
- Инструкции, выражения и операторы в Python
- Инструкции присваивания в Python
- Условная инструкция if в Python
- К содержанию учебника