== != is
Поговорим о разнице между оператором "==" и ключевым словом "is". И про коробочки и бирочки не забудем.
Реклама
Поговорим о разнице между оператором "==" и ключевым словом "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
Пройдёмся сверху вниз по коду примера:
- Мы создали список из трех цифр и записали его в переменную list_one.
- Далее создали точно такой же список и записали его в переменную list_two.
- Затем на ячейку, к которой уже “прикреплена” переменная list_one, "вешаем" еще одну переменную list_three.
- Наконец, в переменную 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
остались нетронутыми, потому что каждый из них спокойно себе лежит в своей “коробке”.
А вот если бы мы пытались проделать нечто подобное со строками или целыми числами, результат снова был бы отличным, но это уже совсем другая история…
Все статьи