5

I just discovered that it's legal in Python to compare arbitrary functions using the operators >, <, >= and <=. This seems a bit silly; I half expected such a comparison to always be False (or throw an exception), but the docs say: "Most other objects of built-in types compare unequal unless they are the same object; the choice whether one object is considered smaller or larger than another one is made arbitrarily but consistently within one execution of a program."

So I did a little experimenting, which implied that perhaps the order in which functions are defined is significant here:

>>> def g():
    pass

>>> def y():
    pass

>>> g > y
False
>>> y > g
True
>>> def r():
    pass

>>> g > r
False
>>> r > g
True
>>> y > r
False
>>> r > y
True
>>> def barfoo():
    pass

>>> barfoo > r > y > g
True

I tried tracking down the source code (noting here that I'm way out of my depth at this point, having all of two months' experience with C). This answer led me to Python/ceval.c, which seems to handle these comparison operators using PyObject_RichCompare() (line 4640). I couldn't find a definition for that function, only a PEP, and that's where I'm stuck.

How might I predict the result of these seemingly nonsensical operations? (And as long as we're here... why would I want to?)

Bonus:

>>> unicode > super > object > type > tuple > str > basestring > slice > frozenset > set > xrange > memoryview > long > list > int > staticmethod > classmethod > float > file > reversed > enumerate > dict > property > complex > bytearray > buffer > bool > zip > vars > unichr > sum > sorted > setattr > round > repr > reload > reduce > raw_input > range > pow > ord > open > oct > next > min > max > map > locals > len > iter > issubclass > isinstance > intern > input > id > hex > hash > hasattr > globals > getattr > format > filter > execfile > eval > divmod > dir > delattr > compile > coerce > cmp > chr > callable > bin > apply > any > all > abs > __import__ > help
True
Community
  • 1
  • 1
Air
  • 8,274
  • 2
  • 53
  • 88

4 Answers4

4

In python2, these type of comparisons is done based on the value of id() of the object:

In [1]: def g():
   ...:     pass

In [2]: def y():
   ...:     pass

In [3]: g > y
Out[3]: True

In [4]: id(g)
Out[4]: 55898312

In [5]: id(y)
Out[5]: 54420736

The value of id() normally depends on the memory address of the function object, which might depend on arbitrary things like the previous history of the garbage collector. Probably for this reason, Python's developers removed this functionality, so comparing functions in Python3 now gives an error:

In [3]: g > y
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/home/xxx/<ipython-input-3-9ebc8ff65838> in <module>()
----> 1 g > y

TypeError: unorderable types: function() > function()

In python 3, comparison for equality is still legal, since it does not depend on the arbitrary value of id():

In [4]: g == y
Out[4]: False

In [5]: g != y
Out[5]: True
Bas Swinckels
  • 18,095
  • 3
  • 45
  • 62
  • True, but does not really answer the question. – NPE Mar 10 '14 at 19:26
  • 1
    It's hard to compare how helpful each of these answers have been, so I'm going to make an arbitrary but consistent choice by accepting this one (early bird gets the worm) – Air Mar 10 '14 at 20:26
  • 2
    Duh, the obvious solution would have been to arbitrarily pick the answer with the highest `id()`. But thanks :) – Bas Swinckels Mar 10 '14 at 20:31
4

The clue is in:

arbitrarily but consistently

Python does the comparison by id, so if

str > list

then

id(str) > id(list)

(And I get False for your bonus!)

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
  • Interesting, I get `False` on ideone as well, but `True` in each of the several Python 2.x environments I have locally available. – Air Mar 10 '14 at 20:11
  • 1
    (I guess that underscores just how arbitrary it is to compare objects this way!) – Air Mar 10 '14 at 20:17
2

First, it is important to note that this behavior is more logical in Python 3:

>>> def f(): pass
...
>>> def g(): pass
...
>>> f < g
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: function() < function()

As for the behavior in Python 2, you already quoted the relevant documentation:

Most other objects of built-in types compare unequal unless they are the same object; the choice whether one object is considered smaller or larger than another one is made arbitrarily but consistently within one execution of a program.

The key piece here is that the order is arbitrary, so to answer you question:

How might I predict the result of these seemingly nonsensical operations?

Don't try to, the docs specifically state that any ordering that you observe cannot be relied upon, and is subject to change for different interpreters, versions, and even different executions of the same program.

As for how this is actually implemented in CPython, is is based on the id() for the functions, which is essentially an address in memory. See How does python compare functions?

Community
  • 1
  • 1
Andrew Clark
  • 202,379
  • 35
  • 273
  • 306
1

I am fairly certain† it uses the built-in function id, which, from its docstring: "Return[s] the identity of an object. This is guaranteed to be unique among simultaneously existing objects. (Hint: it's the object's memory address.)"

This would be consistent with the invariant you found for the inequality operators from the docs.

On my system, it seems dependent on the order in which you define the functions. An example:

In [1]: def g():
   ...:     pass
   ...: 
In [2]: def y():
   ...:     pass
   ...: 
In [3]: g > y
Out[3]: False
In [4]: y > g
Out[4]: True
In [5]: (id(g), id(y))
Out[5]: (171432676, 171432844)
In [6]: id(g) > id(y)
Out[6]: False

Versus:

In [1]: def y():
   ...:     pass
   ...: 
In [2]: def g():
   ...:     pass
   ...: 
In [3]: g > y
Out[3]: True
In [4]: y > g
Out[4]: False
In [5]: (id(g), id(y))
Out[5]: (165088140, 165087972)
In [6]: id(g) > id(y)
Out[6]: True

The specifics of this are closely related to how Python allocates memory for dynamic objects on its private heap, but as the docs themselves state, it is an arbitrary metric for comparing objects that aren't intuitively orderable such as functions.

†At least for the CPython implementation

George Bahij
  • 597
  • 2
  • 9