Python’s misleading readability

Tuesday 23 January 2018This is over six years old, but it's still good.

One of the things that has made Python successful is its readability. Code is clear and easy to understand. One of the reasons is that Python uses words for a few things that other languages use symbols for. But sometimes the readability is misleading. Beginners construct valid Python expressions that don’t do what they seem like they should do.

Let’s say you want to know if your variable x is equal to 17. You could do:

if x is 17:

This might work. But then if you try:

if name is "Ned":

it doesn’t work? What!? Why not? It’s so clear.

The problem is that “is” doesn’t check two values for equality, it checks if the left and right side are precisely the same object. But you can have two different string objects, each of which has the value “Ned”. You don’t use “is” to check equality, you use “==”:

if name == "Ned":

It’s not just strings: numbers can also do surprising things:

>>> 1000 + 1 is 1001
False

“x is 17” is more English-like than “x == 17”, but it isn’t right. This is one time that Python’s famed readability leads you to the wrong construct.

Another example: you need to know if the answer was either “y” or “yes”, so you try this:

if answer == "y" or "yes":
    print("Thanks")

and now your program doesn’t work. No matter what answer is, it prints “Thanks.” Why?

The “or” operator is for combining boolean (true/false) expressions. The result is true if either of its operands is true. So your code is equivalent to:

if (answer == "y") or ("yes"):
    print("Thanks")

If answer is “y”, then the if will be true. If answer isn’t “y”, then the or will consider the right-hand side, “yes”. Strings are true if they are not empty, so “yes” is always true. So the if condition will always be true, no matter what value answer has.

The right ways to do this are:

if answer == "y" or answer == "yes":
    print("Thanks")

or if you want to be fancier,

if answer in {"y", "yes"}:
    print("Thanks")

(a list or a tuple would also work here instead of a set, though then you get into philosophical debates about how many data structures fit on the head of a pin.)

Don’t get me wrong, I agree that Python is very readable. And every language has constructs that seem like they should work, but don’t. You have to study well, and be careful to use your chosen language properly.

Comments

[gravatar]
You could explain why the 'x is 17' example returns True because it works only for numbers in a certain range - it can be confusing if someone does not know that Python saves some numbers as int objects into memory at the startup and does not create new instances of those when assigning new variables.
[gravatar]
I'm going to skip the details about why "x is 17" works but "x is 257" doesn't work. It's interesting, and fun if you are into programming language implementation details: there's a Stack Overflow question about it with lots of details. But it doesn't really add to the advice to stay away from "is".
[gravatar]
The problems the author has addressed are made out of thin air. I'll quote the Python documentation for the is operator: "The operators is and is not test for object identity: x is y is true if and only if x and y are the same object. Object identity is determined using the id() function." It does not check equality, it checks identity.
The second case I will leave without a comment, this is programming basics and you definitely need to learn more about Python if you think in 'if a == 1 or 2' constructions.
[gravatar]
@Boris: these examples are not invented: they are common mistakes beginners make. They make them because they don't understand yet. I'm not sure what you mean by "made out of thin air."
[gravatar]
This works for "y" and "yes" but beware it also works for "nay" and "no way jose" :)

if "y" in answer: print("Thanks")

@Boris, Ned Batchelder is a real polite guy in answering you. He has a 16,100 score on Stackoverflow, what's yours?
[gravatar]
@Bill, I'd rather not use Stack Overflow score in an appeal to authority, for a few reasons :)
[gravatar]
I just wanted to second Ned's claims — I think Python is generally a great choice for beginners but it's really hard to appreciate how many things we take for granted are easy only because we internalized key concepts decades ago.
[gravatar]
These problems aren't specific to Python -- you could make similar ones in just about any programming language.

Calling it out as a problem specifically in Python feels weird.
[gravatar]
Ned I assure you this is valuable. I help admin one of the largest Python groups on Facebook, and this topic is a common one, despite what some may think
[gravatar]
Hello Ned,
I am newbie so please go easy if I am mis-understanding what you say. When I tried this, here's what I get:
>>> 100+1 is 101
True
>>> 1000 + 1 is 1001
False
>>> 1000+1 is 1001
False
>>> 100+1 is 101
True
>>> 100 + 1 is 101
True
>>> name = "ted"
>>> name is "ted"
True
>>>
How is this happening?
I am using python 3.6.2
[gravatar]
@Ritzy: the important thing to understand is that "is" checks a condition we almost never care about: whether two objects (in your case integers) are the exact same object in memory. It doesn't really matter why 1000+1 produces a different integer object than 1001. The important thing is that they both have the value 1001, which you can check with ==.

The mechanics behind how Python decides to make new integers, and when to reuse old integer objects, is complex, implementation- and version-dependent, and changes depending on seemingly irrelevant details, like whether you are in the repl or a .py file. If you are interested in implementation trivia, the Stack Overflow question I linked to earlier in the comments includes details.
[gravatar]
@Hannele: yes it's true of other languages. My last paragraph makes that point. The reason to talk about Python is 1) it is the world I am involved in, and 2) it lets us talk concretely about specific examples, with explanations about why they don't work as they seem. I don't have the expertise to provide detailed examples in other languages, and speaking in vague generalities wouldn't be an interesting blog post. :)
[gravatar]
Actually, I think this may be more true of Python than other languages, because of Python's high readability. C/Java/Javascript/Lisp/Ruby/Haskell don't have "is", "and" and "or" as keywords. Right off the bat, you are in punctuation-land, so you don't fall into the trap of thinking that these English-seeming words work like English words.

Don't get me wrong: I think Python's readability is one of its strongest points. Other languages will seem more foreign to new learners. Sure, beginners try things in other languages, and they don't work as they expect, but it's not because they typed out some English and it didn't work like English.
[gravatar]
Ruby does have "and" and "or" as keywords, and it's even more confusing because they have different places in the hierarchy from && and ||. Which reminds me of another point confusion: bitwise versus logical operators.
[gravatar]
Aren't you confusing readability with writeability?

Yes, it's not as trivial to write correct Python code as one would think.

But the real advantage of Python is its readability: Given clean, idiomatic code someone with some programming background but no knowledge of that specific language will find it is much easier to understand Python than say, Perl, C++, Haskell.

Add a comment:

Ignore this:
Leave this empty:
Name is required. Either email or web are required. Email won't be displayed and I won't spam you. Your web site won't be indexed by search engines.
Don't put anything here:
Leave this empty:
Comment text is Markdown.