Author avatar

Luke Lee

Comparing Unrelated Types

Luke Lee

  • Apr 17, 2019
  • 5 Min read
  • Apr 17, 2019
  • 5 Min read
Data Science


1>>> x = range(50)
2>>> x < 20

What's the value of x < 20?

I'll kill the suspense for you, it is False.

I know, it didn't make sense to me either. I don't know what comparing Integers to Lists means semantically, but I would like to know why Python translates this comparison to False.

I thought the answer could be relevant for an upcoming talk. After all, the implementation of the < operator is handled by the less-than dunder method for the list type.


  1. My first thought was that the < operator was comparing with len() for the list, which would make x < 51 True. Result: wrong.

  2. Maybe Python was using the list's id. This would sort of make sense since each object has a unique Long ID -- an ideal comparison tool. Result: wrong.
1>>> id(x)
3>>> x < 170461901

To the Source!

At this point, the mystery drove me to download the Python 2.7 source.

I could have scoured the Internet for a quick solution, but I wanted to spend a little time digging through the Python 2.7 source.

I started my search by looking up the implementation for '<' in the list object. That's when the fun of tracing C function pointers and macros began...

I'll save you the time and just provide a summary of what I found. The comparison operation ends in the default_3way_compare function.

The following snippet of code dictates the outcome of all list-integer comparisons by deciding that our list is always greater than (not less than) all integers:

1/* different type: compare type names; numbers are smaller */
2if (PyNumber_Check(v))
3    vname = "";
5    vname = v->ob_type->tp_name;
6if (PyNumber_Check(w))
7    wname = "";
9    wname = w->ob_type->tp_name;
10c = strcmp(vname, wname);
11if (c < 0)
12    return -1;
13if (c > 0)
14    return 1;

Surprised? Confused? I was both.


After some more research, I found that the comparison is based on the actual name of the types! So, in our case List is greater alphabetically than Integer! This pattern persists in most type comparisons.

Not exactly the rationale I was hoping for, but at least we have an answer.

However, now that we know what is happening, see if you can figure out the answer to a few more puzzles:

1>>> [] < ()
2>>> [] < {}

Spoiler alert

A List is less than a Tuple, remember 'L' comes before 'T' in the alphabet, and of course by the same logic a List is NOT less than a Dict.

Easter Egg

None is really small in comparison:

1/* None is smaller than anything */
2if (v == Py_None)
3    return -1;
4if (w == Py_None)
5    return 1;

Python 3

Until now, I was using Python 2.7. I wanted to know what Python 3 would do in this case.

1>>> x = range(50)
2>>> x < 20
3Traceback (most recent call last):
4File "<stdin>", line 1, in <module>
5TypeError: unorderable types: range() < int()
6>>> [] < ()
7Traceback (most recent call last):
8File "<stdin>", line 1, in <module>
9TypeError: unorderable types: list() < tuple()
10>>> [] < {}
11Traceback (most recent call last):
12File "<stdin>", line 1, in <module>
13TypeError: unorderable types: list() < dict()

Nice! As you see above, unrelated type comparisons now lead to TypeErrors, a result that's more comprehensible than alphabetical order. So this little quirk is fixed.

When you upgrade your awesome application to Python 3 comparing completely different types will cause the interpreter to complain loudly, and it should. Then, you are free to forget everything you learned in this post. :)

Suggested Reading

If you are interested, I found a great stackoverflow post confirming all of the above research after I finished with my digging. You can also look into the Python documentation to find the same answer. Nonetheless, taking the chance to look through Python source code proved to be an enlightening experience.

A side note: this implementation is an implementation choice by CPython. Your results with other implementations of Python like pypy could be different.