0

I inadvertently typed time.clock<() with the Python 2.7 interpreter response being: True. The following code exemplifies the behavior:

>>> repr(time.clock)
'<built-in function clock>'
>>> time.clock<()
True

Moreover:

>>> import sys
>>> sys.maxint < ()
True

>>> map(lambda _:0<_,((),[],{}))
[True, True, True]

In contrast:

>>> 1<set(())
TypeError: can only compare to a set

Question: Besides why, is there a practical meaning or purpose of an empty list, tuple or dict evaluating as if were greater than any number?


Update:

  • Viktor pointed out that memory-addresses are compared by default:

    >>> map(lambda _:(id(0),'<',id(_)),((),[],{}, set([])))

    [(31185488L, '<', 30769224L), (31185488L, '<', 277144584L), (31185488L, '<', 279477880L), (31185488L, '<', 278789256L)]

Despite the seeming order, this is incorrect.


  • Martijn Pieters points out that:

Without an explicit comparison operator defined, Python 2 compares by Numbers and Type-names, with numbers having the lowest precedence.

This does not hint at what exact internal methods are being invoked. See also this helpful but inconclusive SO thread:

In an IPython 2.7.5 REPL

>>> type(type(()).__name__)
Out[15]: str

>>> type(()) < 10
Out[8]: False
>>> 10 < type(())
Out[11]: True
#as described
>>> type(()) < type(())
Out[9]: False
>>> type(()) == type(())
Out[10]: True

However:
>>> 'somestr' .__le__(10)
Out[20]: NotImplemented
>>> 'somestr' .__lt__(10)
Out[21]: NotImplemented

>>> int.__gt__
Out[25]: <method-wrapper '__gt__' of type object at 0x1E221000>
>>> int.__lt__
Out[26]: <method-wrapper '__lt__' of type object at 0x1E221000>

>>> int.__lt__(None)
Out[27]: NotImplemented
    #.....type(...), dir(...), type, dir......
#An 'int' instance does not have an < operator defined
>>> 0 .__lt__
Out[28]: AttributeError: 'int' object has no attribute '__lt__'

#int is actually a subclass of bool
>>>int.__subclasses__()
Out: [bool]
#str as the fallback type for default comparisons
>>> type(''.__subclasshook__)
Out[72]: builtin_function_or_method
>>> dir(''.__subclasshook__)
Out[73]: 
['__call__',
 '__class__',
 '__cmp__',
 '__delattr__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__le__',
 '__lt__',
 '__module__',
 '__name__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__self__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__']
#IPython is subclassing 'str' 
>>> str.__subclasses__()
Out[84]: [IPython.utils.text.LSString]
Community
  • 1
  • 1
Lorenz Lo Sauer
  • 23,698
  • 16
  • 85
  • 87
  • possible duplicate of [How do Python comparison operators < and > work with a function name as an operand?](http://stackoverflow.com/questions/18387938/how-do-python-comparison-operators-and-work-with-a-function-name-as-an-opera) – Ashwini Chaudhary Aug 29 '13 at 17:02
  • Note that in python3 all these comparisons raise `TypeError`: `>>> 0 < () Traceback (most recent call last): File "", line 1, in TypeError: unorderable types: int() < tuple()` – Bakuriu Aug 29 '13 at 17:04
  • Viktor's answer was incorrect. – Ashwini Chaudhary Aug 29 '13 at 17:12
  • @Bakuriu I too was under the impression that it should be so. Thanks for reassuring that. – Lorenz Lo Sauer Aug 29 '13 at 17:12
  • This is one of the awkward things that in python3 were fixed with a backward incompatible change(and with good reasons). If you don't have compelling reasons to stay with python2(e.g. cannot upgrade the box, or missing dependencies) I'd strongly suggest to start using python3(but *do* read the various "what's new") – Bakuriu Aug 29 '13 at 17:31

1 Answers1

5

In Python 2, when comparing different types, python sorts numeric types before everything else, and between the rest sorts types by type name.

Thus, integers sort before tuples, but instances of class Foo will sort after instances of class Bar.

Python 3 does away with this madness; comparing different types results in a TypeError instead:

>>> 10 < ()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: int() < tuple()

The Python set() type has overloaded the > operator by implementing the __gt__ or 'greater then' magic method; it is called for the 1 < set() expression because the int type has no __lt__, lower-then and Python tests the inverse in that case; after all, x < y is true if y > x is true.

The set.__gt__() hook raises a TypeError when the other operand is not a set:

>>> 1 .__lt__(set())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'int' object has no attribute '__lt__'
>>> set().__gt__(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only compare to a set

The overloaded > (greater then) operator for sets is used to test if the left-hand operand is a proper superset of the right-hand operand. (Technically, set objects implement the C-API PyTypeObject.tp_richcompare function, not the __gt__ hook directly, but the __gt__ hook translates to a tp_richcompare call in that case automatically).

When an overloaded comparison method (one of .__lt__(), .__le__(), .__eq__(), . __ne__(), . __gt__(), . __ge__(), or . __cmp__()) returns the NotImplemented singleton object this signals that the comparison is not supported and Python falls back to the default behaviour. This default behaviour, as already stated in How do Python comparison operators < and > work with a function name as an operand? differs between Python 2 and 3.

For Python 3, a comparison hook returning NotImplemented causes Python to raise TypeError:

>>> class Foo():
...     def __lt__(self, other): return NotImplemented
... 
>>> Foo() < Foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: Foo() < Foo()

Python 2 is more stubborn and when NotImplemented is returned or no hooks have been implemented, the C code ends up in the default_3way_compare() C function, which:

  • Orders by memory addresses when the types of both objects are the same (line 768-776)
  • Orders None before anything (line 780-783)
  • Orders numbers before other types (PyNumber_Check tests set type name to empty, lines 786-793)
  • Orders by typename (v->ob_type->tp_name and w->ob_type->tp_name in lines 786-793)
  • If the type names are the same, orders by memory address of the type objects (lines 800 and 801).
Community
  • 1
  • 1
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 2
    Also the `set` example fails because `<` is *not* the less than operators for sets, it's treated as the "is sub set" operator, which obviously doesn't make sense with non-set operands. (I believe the python docs somewhere explicitly state that the language does not define the semantics of `<`, `>` etc., hence this difference) – Bakuriu Aug 29 '13 at 17:07
  • @Bakuriu: I am explaining what `lt` stands for, not what the operator now is used for in sets. – Martijn Pieters Aug 29 '13 at 17:17
  • @MartijnPieters Thanks. I still cannot place the comparison steps though. Please help me out, as the long-standing Pythionist that you are and I will gladly accept your answer. See also my update. – Lorenz Lo Sauer Sep 07 '13 at 16:17
  • 1
    @LoSauer: Boy, you dug yourself a hole into wonderland, didn't you? I'll see what I can do when I have some time. – Martijn Pieters Sep 07 '13 at 18:33