Non-boolean rich comparisons in tuple work inconsistently

class A:
    def __lt__(self, o):
        return "hello"

a = A()
b = A()
print(a < b)  # hello
print(a < a)  # hello

a = (A(),)
b = (A(),)
print(a < b)  # hello
print(a < a)  # False

I have no idea why the last one is False. It looks like it could be a bug due to optimization (maybe?). Could anyone explain why this is happening?

ver: Python 3.12.3

a < a is False because tuples form a strict total order, which requires a tuple not be less than itself. That means we don’t need to compare a[0] to a[0] to determine the answer; it’s false for any given tuple a. Only when we compare two different tuples do we need to compare corresponding elements in lexicographical comparison.

2 Likes

Sorry, I figured it out. It’s not a bug.

Since A doesn’t define __eq__, it defaults to object.__eq__, which compares object IDs to check for identity.

This has a significant impact on lexicographic comparison in tuples. The IDs of a[0] and a[0] are the same, but a[0] and b[0] have different IDs. As a result, a < a defaults to comparing the tuple lengths, which ultimately yields False.

1 Like

Thanks for your response…! It looks like our answers overlapped since we replied at the same time. Your explanation also makes a lot of sense from a broader perspective. Thank you again for taking the time to respond!

And to add to this, suppose that the object defines equality in some way - the same result would be seen any time the tuples’ corresponding elements compare equal. To get __lt__ to be called, you need to define __eq__ to return False.

I think it’s not so much about object identifiers, but tuplerichcompare does go straight to lexicographical comparison. However, it only cares about the boolean result, and "hello" is a truthy value, so it exhausts all comparisons, and so falls back on the lengths as you point out.

I thought there would be a short path that tests for and handles object identity, but apparently not.