'is' vs '==' in Python
Python has two very similar operators for checking whether two objects are equal. These two operators are is
and ==
.
They are usually confused with one another because with simple data types, like int
s and string
s (which many people start learning Python with) they seem to do the same thing:
x = 5
s = "example"
print("x == 5: " + str(x == 5))
print("x is 5: " + str(x is 5))
print("s == 'example': " + str(s == "example"))
print("s is 'example': " + str(s is "example"))
Running this code will result in:
x == 5: True
x is 5: True
s == 'example': True
s is 'example': True
This shows that ==
and is
return the same value (True
) in these cases. However, if you tried to do this with a more complicated structure:
some_list = [1]
print("some_list == [1]: " + str(some_list == [1]))
print("some_list is [1]: " + str(some_list is [1]))
This would result in:
some_list == [1]: True
some_list is [1]: False
Here it becomes obvious that these operators aren't the same.
The difference comes from the fact that
is
checks for identity (of objects), while==
checks for equality (of value).
Here's another example that might clarify the difference between these two operators:
some_list1 = [1]
some_list2 = [1]
some_list3 = some_list1
print("some_list1 == some_list2: " + str(some_list1 == some_list2))
print("some_list1 is some_list2: " + str(some_list1 is some_list2))
print("some_list1 == some_list3: " + str(some_list1 == some_list3))
print("some_list1 is some_list3: " + str(some_list1 is some_list3))
This results in:
some_list1 == some_list2: True
some_list1 is some_list2: False
some_list1 == some_list3: True
some_list1 is some_list3: True
As we can see, some_list1
is equal to some_list2
by value (they're both equal to [1]
]), but they are not identical, meaning they aren't the same object, even though they have equal values.
However, some_list1
is both equal and identical to some_list3
since they reference the same object in memory.
Mutable vs Immutable Data Types
While this part of the problem now might be clear (when we have named variables), another question might pop up:
How come
is
and==
behave the same with unnamedint
andstring
values (like5
and"example"
) but don't behave the same with unnamed lists (like[1]
)?
There are two kinds of data types in Python - mutable and immutable.
- Mutable data types are data types which you can "change" over time
- Immutable data types stay the same (have the same memory location, which is what
is
checks) once they are created
Mutable data types are: list
, dictionary
, set
, and user-defined classes.
Immutable data types are: int
, float
, decimal
, bool
, string
, tuple
, and range
.
Like many other languages Python handles immutable data types differently than mutable types, i.e. saves them in memory only once.
So every 5
you use in your code is the exact same 5
you use in other places in your code, and the same goes for string literals you use.
If you use the string "example"
once, every other time you use "example"
it will be the exact same object. See this Note for further clarification.
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
We will be using a Python function called id()
which prints out a unique identifier for each object, to take a closer look at this mutability concept in action:
s = "example"
print("Id of s: " + str(id(s)))
print("Id of the String 'example': " + str(id("example")) + " (note that it's the same as the variable s)")
print("s is 'example': " + str(s is "example"))
print("Change s to something else, then back to 'example'.")
s = "something else"
s = "example"
print("Id of s: " + str(id(s)))
print("s is 'example': " + str(s is "example"))
print()
list1 = [1]
list2 = list1
print("Id of list1: " + str(id(list1)))
print("Id of list2: " + str(id(list2)))
print("Id of [1]: " + str(id([1])) + " (note that it's not the same as list1!)")
print("list1 == list2: " + str(list1 == list2))
print("list1 is list2: " + str(list1 is list2))
print("Change list1 to something else, then back to the original ([1]) value.")
list1 = [2]
list1 = [1]
print("Id of list1: " + str(id(list1)))
print("list1 == list2: " + str(list1 == list2))
print("list1 is list2: " + str(list1 is list2))
This outputs:
Id of s: 22531456
Id of the String 'example': 22531456 (note that it's the same as the variable s)
s is 'example': True
Change s to something else, then back to 'example'.
Id of s: 22531456
s is 'example': True
Id of list1: 22103504
Id of list2: 22103504
Id of [1]: 22104664 (note that it's not the same as list1!)
list1 == list2: True
list1 is list2: True
Change list1 to something else, then back to the original ([1]) value.
Id of list1: 22591368
list1 == list2: True
list1 is list2: False
We can see that in the first part of the example, s
returned to the exact same "example"
object it was assigned to at the beginning, even if we change the value of s
in the meantime.
However, list
does not return the same object whose value is [1]
, but a whole new object is created, even if it has the same value as the first [1]
.
If you run the code above, you are likely to get different IDs for the objects, but the equalities will be the same.
When are 'is' and '==' Used Respectively?
The is
operator is most commonly used when we want to compare the object to None
, and restricting its usage to this particular scenario is generally advised unless you really (and I do mean really) want to check whether two objects are identical.
Besides, is
is generally faster than the ==
operator because it simply checks for integer equality of the memory address.
Important note: The only situation when is
works exactly as might be expected is with singleton classes/objects (like None
). Even with immutable objects, there are situations where is
does not work as expected.
For example, for large string
objects generated by some code logic, or large int
s, is
can (and will) behave unpredictably. Unless you go through the effort of interning (i.e. making absolutely sure that only one copy of a string
/int
/etc. exists), all the various immutable objects you plan to use, is
will be unpredictable.
The bottom line is: use
==
in 99% of cases.
If two objects are identical they are also equal, and that the opposite isn't necessarily true.
Overriding '==' and '!=' Operators
Operators !=
and is not
behave in the same way as their "positive" counterparts do. Namely, !=
returns True
if objects don't have the same value, while is not
returns True
if the objects are not stored in the same memory address.
One other difference between these two operators is that you can override the behavior of ==
/!=
for a custom class, while you can't override the behavior of is
.
If you implement a custom __eq()__
method in your class, you can change how the ==
/!=
operators behave:
class TestingEQ:
def __init__(self, n):
self.n = n
# using the '==' to check whether both numbers
# are even, or if both numbers are odd
def __eq__(self, other):
if (self.n % 2 == 0 and other % 2 == 0):
return True
else:
return False
print(5 == TestingEQ(1))
print(2 == TestingEQ(10))
print(1 != TestingEQ(2))
This results in:
False
True
True
Conclusion
In short, ==
/!=
checks for equality (by value) and is
/is not
checks whether two objects are identical, i.e. checks their memory addresses.
However, avoid using is
unless you know exactly what you're doing, or when dealing with singleton objects like None
, since it can behave unpredictably.