Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How comparator works for objects that are not comparable in python?

Tags:

python

I have defined a list as below:

list = [1,3,2,[4,5,6]]

then defined a comparator method as below:

def reverseCom(x,y):
   if(x>y):
        return -1
   elif(x<y):
        return 1
   else:
        return 0

Now I have sorted the list using reverseCom:

list.sort(reverseCom)
print list

Result : [[4, 5, 6], 3, 2, 1]

Though the element [4, 5, 6] is not comparable with other elements of the list. How its not throwing any error ?

Do you can help me to understand that how sort works with the user defined comparator in python ?

like image 439
Arijit Avatar asked Dec 26 '15 19:12

Arijit


Video Answer


2 Answers

This is a Python 2 quirk. In Python 2, numeric and non numeric values are comparable, and numeric values are always considered to be less than the value of container objects:

>>> 1 < [1]
True
>>> 1 < [2]
True
>>> 1558 < [1]
True
>>> 1 < {}
True

when comparing two containers values of different types, on the other hand, it is the name of their type that is taken into consideration:

>>> () < []
False
>>> 'tuple' < 'list'
False

>>> {} < []
True
>>> 'dict' < 'list'
True

This feature, however, has been dropped in Python 3, which made numeric and non-numeric values no longer comparable:

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

EDIT: this next explanation is fully experimentation-based, and I couldn't find sound documentation to back it up. If any one does find it, I'd be glad to read through it.

It appears Python 2 has even more rules when it comes to comparison of user-defined objects/non-container objects.

In this case it appears that numeric values are always greater than non-numeric non-container values.

>>> class A: pass
...
>>> a = A()
>>> 1 > a
True
>>> 2.7 > a
True

Now, when comparing two objects of different, non-numeric, non-container types, it seems that it is their address that is taken into account:

>>> class A: pass
...
>>> class B: pass
...
>>> a = A()
>>> a
<__main__.A instance at 0x0000000002265348>
>>> b = B()
>>> b
<__main__.B instance at 0x0000000002265048>
>>> a < b
False
>>> b < a
True

Which is really bananas, if you ask me.

Of course, all that can be changed around if you care to override the __lt__() and __gt__() methods inside your class definition, which determine the standard behavior of the < and > operators.

Further documentation on how these methods operate can be found here.

Bottomline: avoid comparison between different types as much as you can. The result is really unpredictable, unintuitive and not all that well documented. Also, use Python 3 whenever possible.

like image 154
zayora Avatar answered Nov 15 '22 12:11

zayora


Your comparator actually works, i.e., does not throw any error:

In [9]: reverseCom([4,5,6],1)
Out[9]: -1

In [10]: reverseCom([4,5,6],2)
Out[10]: -1

In [11]: reverseCom([4,5,6],3)
Out[11]: -1

The reason why it works is, list instances always bigger than int instances:

In [12]: [1,2,3] > 5
Out[12]: True

In [13]: ['hello'] > 5
Out[13]: True

In [14]: [] > -1
Out[14]: True
like image 26
Sait Avatar answered Nov 15 '22 12:11

Sait