Flirting with TypeError
I was reading Peter Norvig's spelling corrector essay and stumbled on this:This [code] works because max(None, i) is i for any integer i.I was all like: wow, Python gonna let this punk get away with that? What's the matter? What's the world coming to?
nigel@codumentary:~$ python Python 3.2.3 (default, Nov 22 1963, 18:30:12) [GCC 4.6.3] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> help Type help() for interactive help, or help(object) for help about object. >>> max(None, 12) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unorderable types: int() > NoneType()That's what the world is coming to: TypeError.
But. There is a huge but.
A Huge But
pnorvig@google:/usr/local/secret-project/popups$ python Python 2.7.3 (default, Apr 20 2012, 22:39:59) [GCC 4.6.3] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> # it works >>> max(None, 12) 12 >>> # there is more >>> class FarmAnimal(object): pass >>> max(lambda x: x + 12, FarmAnimal) <class '__main__.FarmAnimal'>Yes, you can compare class to lambda function in Python2. Python3 throws an exception.
I think that's a really weird behaviour. I mean Python2 allowing it, not Python3 throwing an exception. What's going on here? Has Guido van Rossum gone completely Brendan Eich? What does Python documentation say?
Read the Docs
To quote the docs:Objects of different types, except different numeric types and different string types, never compare equal; such objects are ordered consistently but arbitrarily (so that sorting a heterogeneous array yields a consistent result)
CPython implementation detail: Objects of different types except numbers are ordered by their type names; objects of the same types that don’t support proper comparison are ordered by their address.Let's check it:
>>> type(FarmAnimal).__name__ 'type'Type name of FarmAnimal is 'type', type name of
>>> type(lambda x: x + 12).__name__ 'function'
lambda x: x + 12
is 'function'. That's why FarmAnimal is greater than lambda x: x + 12 (string 'type' is greater than string 'function').What about this:
>>> max(None, 12)Is it because string 'int' is greater than string 'NoneType'?
12
Documentation mentions that numeric types are exception to the rule - not very helpful.
Let's have a look at CPython2 source code.
Read the Source
The source says:static int
default_3way_compare(PyObject *v, PyObject *w) { ... ... /* None is smaller than anything */ if (v == Py_None) return -1; if (w == Py_None) return 1; /* different type: compare type names; numbers are smaller */ if (PyNumber_Check(v)) vname = ""; else vname = v->ob_type->tp_name; if (PyNumber_Check(w)) wname = ""; else wname = w->ob_type->tp_name; c = strcmp(vname, wname); if (c < 0) return -1; if (c > 0) return 1; /* Same type name, or (more likely) incomparable numeric types */ return ((Py_uintptr_t)(v->ob_type) < ( Py_uintptr_t)(w->ob_type)) ? -1 : 1; }
Source is good
I was wrong. The answer to question
Is it because string 'int' is greater than string 'NoneType'?
is no.
12 is greater than None not because 'int' is greater than 'NoneType'.
12 is greater than None because of this C code:
/* None is smaller than anything */ if (v == Py_None) return -1; if (w == Py_None) return 1;Anything is greater than None in CPython2. None is Python's absolute zero. Here's the proof:
>>> cow = FarmAnimal() >>> type(cow).__name__
'FarmAnimal' >>> 'FarmAnimal' > 'NoneType'
False >>> # Comparing to None ignores type names >>> max(None, cow) <__main__.FarmAnimal object at 0xdeadbeef>
Twitterize it
CPython2 comparison tower is:1) None
2) Numeric types ordered by type name
3) Non-numeric types ordered by type name
Unleash Hell
And that's why max(None, i) is i for any integer i.Now you can compare all sorts of stuff in your Python2 programs. Apples to tuples, ashes to exceptions, numbers to None.
Unleash hell!
You should be aware of two things though:
1) Hell won't work in Python3
2) Nobody wants extra troubles while porting code to Python3