reptilicus
reptilicus

Reputation: 10407

Custom class ordering: no error thrown, what is Python testing for?

Without specifying the equality comparison properties of objects, Python is still doing something when using > and <. What is Python actually comparing these objects by if you don't specify __gt__ or __lt__? I would expect an unsupported operand error here, as you get when trying to add two objects together without defing __add__.


In [1]: class MyObject(object):
   ...:     pass
   ...: 
In [2]: class YourObject(object):
   ...:     pass
   ...: 
In [3]: me = MyObject()
In [4]: you = YourObject()
In [5]: me > you
Out[5]: False
In [6]: you > me
Out[6]: True

Upvotes: 8

Views: 225

Answers (3)

Martijn Pieters
Martijn Pieters

Reputation: 1123970

An arbitrary order is imposed for objects. The ordering is only defined to be stable within a program execution.

This means it's up to the Python implementation to define an ordering when comparing arbitrary objects. CPython uses the memory address if the types are the same (from the C source):

if (v->ob_type == w->ob_type) {
    /* When comparing these pointers, they must be cast to
     * integer types (i.e. Py_uintptr_t, our spelling of C9X's
     * uintptr_t).  ANSI specifies that pointer compares other
     * than == and != to non-related structures are undefined.
     */
    Py_uintptr_t vv = (Py_uintptr_t)v;
    Py_uintptr_t ww = (Py_uintptr_t)w;
    return (vv < ww) ? -1 : (vv > ww) ? 1 : 0;
}

The same value is the basis for the id() function, and is also represented in the default repr() string for custom classes, so it may appear that the repr() of classes determine ordering. It's only the memory address that does.

For objects that are not the same type, the type name is used instead (with number-like types sorting before others), and if the types differ but their names are the same, the code falls back to the memory address of the type (as opposed to the memory address of the instance when the types are the same).

This implicit ordering has been considered an error in the language, and has been remedied in Python 3:

The ordering comparison operators (<, <=, >=, >) raise a TypeError exception when the operands don’t have a meaningful natural ordering.

This applies to custom classes that do not implement the necessary ordering hooks:

>>> class Foo(): pass
... 
>>> Foo() < Foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: Foo() < Foo()

Upvotes: 6

dave mankoff
dave mankoff

Reputation: 17779

EDIT: See the update after reading! The completely correct answer is "undefined, but consistent".

It's testing repr(me) vs repr(you) which essentially is doing string comparison. Here is an easy to see example:

class A(object): pass

class B(object): pass

x = A() # <__main__.A object at 0x7f7014e4e2d0>
y = B() # <__main__.B object at 0x7f7014e4e310>
z = A() # <__main__.A object at 0x7f7014e4e390>

x < z < y # True (assuming that the memory addresses are ordered as above)

I agree that this seems a little strange to me. Perhaps there's a good reason for this that I am not aware of?

UPDATE: I am wrong about repr, though it does look like it uses the base object repr in test. http://docs.python.org/2/reference/expressions.html#not-in

There is a line there that states "The choice whether one object is considered smaller or larger than another one is made arbitrarily but consistently within one execution of a program." In other words, it might be using repr but that's not guaranteed. It will, however, be consistently done.

Upvotes: 4

glglgl
glglgl

Reputation: 91119

Python uses the object's repr() if there are no rich comparison operators. (At least, this is valid for Py2; I don't know for Py3).

So if you do

class A(object): pass
class B(object): pass
l = [A() if i % 2 else B() for i in range(1000)]
l.sort()

the objects are sorted by their repr(), the A() coming first and then the B().

Upvotes: 0

Related Questions