avatarDeck451

Summary

The article discusses the nuanced differences between the "==" equality operator and the "is" identity operator in Python, emphasizing the importance of understanding when to use each for optimal coding practices.

Abstract

In Python, the "==" and "is" operators serve distinct purposes: "==" checks for equality, while "is" checks for identity, meaning whether two variables reference the same object in memory. The article illustrates this through examples with integers and lists, highlighting that integers can sometimes reference the same object due to their immutability, leading to "is" returning True. In contrast, lists, being mutable, always reference different objects, resulting in "is" returning False even if the lists contain the same elements. The article also provides insights into Python's memory management and offers style guide recommendations from PEP 8, advocating for the judicious use of "is" and "is not" when comparing to singletons like None and for clarity in boolean contexts.

Opinions

  • The author suggests that the confusion between "==" and "is" is common but can be clarified with a deeper understanding of Python's memory handling.
  • The article implies that Python's implementation, particularly CPython, can influence the behavior

Python Up Your Code: Identity vs Equality

Differences between the “==” and the “is” operators in Python

Photo by Maximalfocus on Unsplash

This should be a rather short little article on a topic I think we’re all bound to stumble upon, sooner or later (preferably sooner than later). As simple as this topic may be, taking a deep dive into it can be quite eye-opening. So, let’s get started!

In Python (and some other languages too, but we’re focusing on Python here), there are a certain number of differences between the equality operators like ==, or != and the identity operators like is and is not. Differences that dictate different use cases for each of the two categories. But somehow confusion sets in. Because in one, very particular, yet very common case, these operators actually behave the same.

But let’s exemplify, to get a better understanding of what’s happening.

# 2 equal integers
a = 2
b = 2
# test for equality
print(a == b)
# test for identity
print(a is b)
Output:
True
True

Interesting, to say the least. Now consider this next example:

# 2 equal lists
l1 = [1, 2, 3]
l2 = [1, 2, 3]
# test for equality
print(l1 == l2)
# test for identity
print(l1 is l2)
Output:
True
False

This time around, we could determine that the lists are equal, but the is operator returned False.

Let’s take another look at the first example, but this time, let’s be a little more thorough:

# 2 equal integers
a = 2
b = 2
# test for equality
print(a == b)
# test for identity
print(a is b)
# print out the object ids
print(id(a))
print(id(b))
Output:
True
True
140495050653968
140495050653968

This example had the == and the is operators both returning True. The equality operator returning True should surprise no one, really. But what’s interesting is the is operator also returning True. Notice how the id() function calls both returned the same object id? Well, now we can get a clear picture of what’s going on.

Turns out, the is operator is testing if the two variables point to the same object in Python’s memory.

  • After the first line, where a = 2, Python created an object with the integer value 2 and assigned it to our variable a;
  • at the second line, we had b = 2. So, what happens here is, Python also assigns the variable b to the same object a is referencing, because integers are immutable, so there’s a chance, depending on your Python implementation, that the same integer object will be used (you can read a whole lot more on this delicate topic right here); what we’re looking at is called a shared reference and you can read more about it in here;

Bottom line, these two variables, in this particular case (I’m using the CPython implementation), have ended up referencing the same integer object. This is why a is b returns True. We could also be looking at the a is b line as a shorthand for id(a) == id(b).

And, while we’ve touched on the Python implementation topic, you may have gotten a bit curious about getting your Python implementation. Here’s how:

import platform
platform.python_implementation()

Now, back to the task at hand, let’s try the same for our two lists:

# 2 equal lists
l1 = [1, 2, 3]
l2 = [1, 2, 3]
# test for equality
print(l1 == l2)
# test for identity
print(l1 is l2)
# print out the object ids
print(id(l1))
print(id(l2))
Output:
True
False
139711984251904
139711982769344

Lists are mutable, so even though we created two lists which are equal in value, Python created two separate, distinct objects. The is operator will check for equality among the ids of the two lists and will naturally return False.

It has to be said, same goes for the != vs is not case. It will yield very similar results.

The official Python PEP 8 style guide has a couple of things to say in the way of recommendations on this subject. They make very good points and are definitely worth not just a read, but to actually be put to use, as these can prevent serious bugs from happening:

Comparisons to singletons like None should always be done with is or is not, never the equality operators.

Also, beware of writing if x when you really mean if x is not None – e.g. when testing whether a variable or argument that defaults to None was set to some other value. The other value might have a type (such as a container) that could be false in a boolean context!

Use is not operator rather than not ... is. While both expressions are functionally identical, the former is more readable and preferred:

# Correct: if foo is not None: # Wrong: if not foo is None:

The first point emphasizes that we should always use is to compare a variable to None instead of ==. Why? Well, I can think of a few reasons:

  • None is a singleton. There can only be one None instance. There’s no point in comparing the value, of a variable to the value of the None object, since what we really want to do is to test if the variable is None;
  • The == operator can be overridden in some custom classes, by simply overriding the __eq__() method and it can be that my_var == None may not return what we may expect it to. The is operator, on the other hand, can’t be (so easily) overridden;
  • is is faster, in general, than ==. It just checks the id() return values for the operands in question.

The second recommendation is that we write things like if variable and expecting it to work as if variable is not None. Writing shorter lines of code may make it seem a bit cleaner, but it most certainly not correct. Look at it this way: what if our variable was 0? See the problem? None isn’t the only thing that evaluates to False in a boolean context. There’s also False, 0, an empty string and so on. All these can evaluate to False and the if variable will evaluate to False, even though none of them is None.

The third recommendation takes readability very seriously and, granted, when you look at the two lines of code, which one would you choose?

Time to wrap this one up. I hope I managed to somehow shed a bit of light on why knowing your operators can save you from endless hours of hunting weird bugs. We should all strive to become better than our yesterday versions of ourselves. We’ll thank ourselves later. To that end, I’ll see you at the next one! Until then, stay safe and happy coding! Cheers!

Deck is a software engineer, mentor, writer, and sometimes even a teacher. With 12+ years of experience in software engineering, he is now a real advocate of the Python programming language while his passion is helping people sharpen their Python — and programming in general — skills. You can reach Deck on Linkedin, Facebook, Twitter, and Discord: Deck451#6188, as well as follow his writing here on Medium.

Python
Programming
Coding
Identity
Equality
Recommended from ReadMedium