Luke Lee

# Comparing Unrelated Types

• Apr 17, 2019
• 8,587 Views
• Apr 17, 2019
• 8,587 Views
Data Science
Python

## Introduction

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

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.

## Assumptions

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)
2170461900
3>>> x < 170461901
4False``````
python

## 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 = "";
4else
5    vname = v->ob_type->tp_name;
6if (PyNumber_Check(w))
7    wname = "";
8else
9    wname = w->ob_type->tp_name;
10c = strcmp(vname, wname);
11if (c < 0)
12    return -1;
13if (c > 0)
14    return 1;``````
c

Surprised? Confused? I was both.

## Surprise!

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>>> [] < {}``````
python

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;``````
c

## 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()``````
python

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. :)