DEV Community

Cover image for Bug of the week #12
piko::tutorial
piko::tutorial

Posted on • Originally published at pikotutorial.com

Bug of the week #12

The error we're handling today is a real-life example simplified to the following Python code:

# user inputs several numbers in form of strings (1, 2, 3 and 123)
# which are then used to construct 2 strings
s1 = "".join(["1", "2", "3"])
s2 = "123"

# if the numbers in the constructed strings are different,
# the program flow should stop
if s1 is not s2:
    raise RuntimeError("s1 is not s2!")

# program flow continues...
Enter fullscreen mode Exit fullscreen mode

The variables s1 and s2 both end up being 123, but when we run this code, we see:

Traceback (most recent call last):
  File "/home/main.py", line 9, in <module>
    raise RuntimeError("s1 is not s2!")
RuntimeError: s1 is not s2!
Enter fullscreen mode Exit fullscreen mode

What's even worse, in the other parts of the app, where s1 and s2 are constructed differently, there are the same comparisons:

s1 = "123"
s2 = "123"

if s1 is not s2:
    raise RuntimeError("s1 is not s2!")

# program flow continues...
Enter fullscreen mode Exit fullscreen mode

But this time the exception is not thrown, so the same comparison seems to give different results for the same s1 and s2 strings.

What does it mean?

The issue comes from misunderstanding the difference between identity and equality in Python. The is operator does not check whether two variables contain the same value. It checks whether both variables point to the exact same object in memory.

  • == asks: “Do these objects have the same content?”
  • is asks: “Are these literally the same object?”

For example:

s1 = "123"
s2 = "123"

print(s1 == s2)   # True
print(s1 is s2)   # maybe True, maybe False
Enter fullscreen mode Exit fullscreen mode

The first comparison checks the values of the strings, so it reliably returns True. The second comparison depends on Python’s internal memory optimizations. Sometimes Python “interns” strings, meaning it reuses the same object for performance reasons. In those cases, s1 is s2 may accidentally return True, but not always.

In this version:

s1 = "".join(["1", "2", "3"])
s2 = "123"

print(s1 == s2)   # True
print(s1 is s2)   # False
Enter fullscreen mode Exit fullscreen mode

Both strings contain exactly the same text, but they were created differently, so Python stores them as separate objects. This makes is unreliable for value comparison.

How to fix it?

Use == whenever you want to compare values. Correct version:

s1 = "".join(["1", "2", "3"])
s2 = "123"

if s1 != s2:
    raise RuntimeError("s1 is not s2!")
Enter fullscreen mode Exit fullscreen mode

Reserve is for cases where you truly want to check object identity. The most common use is comparing with None:

if value is None:
    print("No value provided")
Enter fullscreen mode Exit fullscreen mode

Top comments (0)