Перейти к контенту

== != is

Нюансы Python Андрей Лебедев 650

Поговорим о разнице между оператором "==" и ключевым словом "is". И про коробочки и бирочки не забудем.

== != is
Нюансы Python Андрей Лебедев 650

Поговорим о разнице между оператором "==" и ключевым словом "is". Если до этого вы не читали мой пост о переменных в питоне и коробочках, сейчас самое время сделать это, потому что мы будем продолжать всё ту же аналогию.

Итак, вспомним, что у нас есть:

  • Переменная - она же бирка на коробочке
  • Коробочка - она же ячейка памяти
  • Заводской номер коробочки - он же адрес ячейки памяти
  • Содержимое коробки - значение
  • Склад - память в целом.

Вспомнили? Отлично. Есть шанс, что если с разработкой не срастётся, всегда можно будет устроиться работать на склад. Шутка, всё обязательно получится, для этого мы здесь.

Оператор "=="

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

Как поставить работнику задачу? Дать ему данные с двух бирок (переменные) и попросить его сравнить содержимое коробок, на которых они наклеены. Рассмотрим на примере.

name = "Стэн"
same_name = "Стэн"
print(name == same_name)

Наш трудяга стремглав летит проверять, что у нас лежит в коробках с бирками name и same_name. А лежит у нас и там, и там одинаковая строка "Стэн". Оператор радостно нам сообщает об этом: True!

Закрепим - оператор "==" сравнивает переменные по значению (содержимое двух коробок с бирками, которые мы ему передали).

Ключевое слово "is"

Помимо работника, у нас в команде есть ключевое слово "is" (пускай на этот раз будет сотрудница). Умеет опять-таки немного, но работу свою выполняет чётко. Ей мы также даём информацию о том, что написано на двух бирках (названия переменных), а она проверяет, наклеены ли они на одну и ту же коробку (ссылаются ли переменные на одну и ту же ячейку памяти).

Рассмотрим на примере.

name = “Стэн"
print(id(name))
same_name = “Стэн"
print(id(same_name))
print(name == same_name)
print(name is same_name)

Из поста про переменные мы помним, что две одинаковые строки, которые мы кладем в две разные переменные, на самом деле будут лежать в одной и той же ячейке памяти, что и видно по адресу, который мы в обоих случаях вывели в консоль print’ом. И нашей сотруднице это видно, о чём она нам и заявляет: True.

То есть ещё раз: оператор "==" показывает, одинаковое ли значение (содержимое) хранится в двух ячейках памяти (коробках), на которые ссылаются две переменные (наклеены две бирки). Ключевое слово "is" показывает, ссылаются ли две переменные (наклеены ли две бирки) на одну и ту же ячейку памяти (коробку), или нет.

А зачем это вообще нужно?

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

Бывает. Рассмотрим на примере:

a = "Стэн"
b = "Стэн"
c = "Ст" + "эн"
d = input('Введите слово "Стэн": ')  # Введем слово "Стэн" (без кавычек)
print(id(a))  # 4352025456
print(id(b))  # 4352025456
print(id(c))  # 4352025456
print(id(d))  # 4353014544
print(a == b)  # True
print(a is b)  # True
print(a == c)  # True
print(a is c)  # True
print(a == d)  # True
print(a is d)  # False

Несложно заметить, что во всех четырёх наших переменных в итоге окажется одна и та же строка (если мы честно ввели слово “Стэн” без кавычек, когда нас об этом попросили). Но в первых трех случаях эта строка оказалась в одной и той же ячейке, на которую “наклеились” три переменные: a, b, c.

Почему же в четвертом случае (для переменной d) потребовалось новая ячейка в памяти? Всё дело в том, как Python пытается экономить память. На этапе компиляции кода он способен сравнивать строки между собой, а также проверять результат простых операций со строками (вроде конкатенации строк “Ст” и “эн”). Так как этот результат соответствует уже существующей в памяти строке “Стэн”, нам незачем создавать еще один такой же объект. Смело клеим бирку на уже созданную ранее коробку со Стэном. Процесс оптимизации работы со строками и некоторыми другими объектами называется “интернированием”, и мы когда-нибудь уделим ему отдельное внимание.

А вот то, что мы вводим с клавиатуры и передаем в переменную d, попадает в память уже в момент работы кода и поэтому для новой строки подбирается новая ячейка.

Советую не верить мне на слово, а «пощупать» всё в PyCharm самостоятельно.

Ещё один пример

list_one = [1, 3, 5]
list_two = [1, 3, 5]
list_three = list_one
list_four = list_one.copy()

print(id(list_one))  # 4509660416
print(id(list_two))  # 4511778176
print(id(list_three))  # 4509660416
print(id(list_four))  # 4510270528

print(list_one == list_two)  # True
print(list_one is list_two)  # False

print(list_one == list_three)  # True
print(list_one is list_three)  # True

print(list_one == list_four)  # True
print(list_one is list_four)  # False

Пройдёмся сверху вниз по коду примера:

  1. Мы создали список из трех цифр и записали его в переменную list_one.
  2. Далее создали точно такой же список и записали его в переменную list_two.
  3. Затем на ячейку, к которой уже “прикреплена” переменная list_one, "вешаем" еще одну переменную list_three.
  4. Наконец, в переменную list_four записываем результат копирования списка из list_one.

Первое, на что стоит обратить внимание, list_one и list_two записались в разные ячейки памяти, что видно и по номерам этих ячеек, и по работе нашей прекрасной сотрудницы "is", которая выдает “False”, хотя её коллега "==" справедливо подтверждает, что содержатся в обеих переменных идентичные списки. Со строками из предыдущего примера в такой же ситуации результат, как мы помним, был другим. Да, такое поведение напрямую связано с особенностями этих типов данных, о чем мы подробно поговорим в следующий раз.

Второе, на что стоит обратить внимание, list_one и list_three “прицепились” к одной и той же ячейке. Это видно по работе функции id(), а также по показаниям наших сотрудников - "==" и "is".

Третий важный момент: когда мы используем функцию copy() для копирования списка, в переменную list_four попадает идентичный список с числами. Но записывается он уже в новую ячейку памяти, что легко видно по результатам работы id(), "==" и "is". Возможно, вы спросите, для чего вообще нужна эта манипуляция c copy(), если можно было бы, как и в случае с list_two, просто вручную пересоздать точно такой же список. Ну, хорошо, когда это можно сделать “на берегу” до запуска кода. А если список попадает к нам каким-то другим образом? Например, вводится через консоль или получается автоматически с какого-нибудь сайта? Если нам нужна точная его копия в другой ячейке памяти (а иногда это просто-таки необходимо), то copy() - один из возможных выходов из ситуации.

Ну и последнее

list_one = [1, 3, 5]
list_two = [1, 3, 5]
list_three = list_one
list_four = list_one.copy()
list_one.append(7)
print(list_one)  # [1, 3, 5, 7]
print(list_two)  # [1, 3, 5]
print(list_three)  # [1, 3, 5, 7]
print(list_four)  # [1, 3, 5]

Работаем всё с теми же четырьмя переменными, в которых находится всё то же самое. К списку в list_one добавляем еще одно число и выводим в консоль все четыре переменные. Для тех, кто внимательно следил за ходом нашей мысли, не должно стать сюрпризом, что изменения, помимо списка в переменной list_one, коснулись и списка в переменной list_three. Почему? Потому что это один и тот же список, который “лежит” в одной и той же коробке, к которой мы прицепили две бирки: list_one и list_three. Списки из list_two и list_four остались нетронутыми, потому что каждый из них спокойно себе лежит в своей “коробке”.

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

Войдите, чтобы оставить комментарий.

Комментариев пока нет.