Is there any implementation of Python2 where ordering is transitive?

724 views Asked by At

Is there any existing implementation of Python2 where ordering is transitive? That is, where it's impossible to see this behaviour without creating user-defined types:

>>> x < y < z < x
True

CPython is not transitive because of this counterexample

x = 'b'
y = ()
z = u'ab'

However, this ordering in CPython is documented as only an implementation detail.

2

There are 2 answers

12
Blender On BEST ANSWER

Every mainstream Python implementation fails in one way or another except for Skulpt, but it's arguably an incomplete implementation.

CPython (and variants), PyPy, and Jython:

>>> 'b' < () < u'ab' < 'b'
True

IronPython:

IronPython internally compares the .NET Object.GetHashCode() hashes of unlike objects, so you can break it by abusing the special handling of int and float comparisons and the fact that the internal hash representation of float('+inf') is less than the hash of [] (I'm not sure how stable this is, so it might not work on every installation of IronPython):

>>> 2**200 < float('+inf') < [] < 2**200
True

CLPython

>>> {1: 3} < {1: 4} < {1: 3}
1
>>> {1: 3} < {1: 3}
0

Skulpt

If you count Skulpt as a complete implementation of Python 2 (it can't compare dictionaries and a few other inconvenient types, and has no unicode type), it actually does work by copying CPython's rules for comparison and conveniently leaving out the unicode type:

# 1. None is less than anything
# 2. Sequence types are greater than numeric types
# 3. [d]ict < [l]ist < [s]tring < [t]uple

>>> None < 0 < {} < [] < '' < ()
True

For CPython 2, you would actually have [t]uple < [u]nicode, but because unicode and str comparisons are handled as a special case, you lose transitivity. Although it's unlikely that Python 2 will get a patch to fix this "bug", I think you can ensure transitivity by just explicitly changing the order from:

[d]ict < [l]ist < [s]tring < [t]uple < [u]nicode

To:

[u]nicode < [d]ict < [l]ist < [s]tring < [t]uple

That way, the special case of str to unicode comparisons doesn't break anything.

6
hynekcer On

Some comparisons are declared as not specified in Python 2.7:

The most important general rules for comparisons are in The Python Language Reference chapter 5. Expressions / 5.9 Comparisons.

The first rules are the well known ones for comparison of numeric types (bool, int, long, float), strings (str, unicode), tuples and lists. The last two rules declare what is not specified:

  • Mappings (dictionaries) compare equal if and only if their sorted (key, value) lists compare equal. [5] Outcomes other than equality are resolved consistently, but are not otherwise defined. [6]
  • 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.

Many specific rules are in Comparison chapter in The Python Standard Library / Built-in Types, referenced above in the question, and in docs about specific types like complex, Decimal or Fraction.

Order comparison is not supported for type complex and it should raise TypeError. Decimal type is compared by value. It is compatible with numbers.Number since Python 2.7. fractions.Fraction is also compared by value.


My reflection: If a relation of comparison can be arbitrary and can be not reproducible within two executions of the program on the same computer, then it is not useful to say about transitivity. All cases above where the order is explicitly not specified in Python 2 should raise TypeError in Python 3.

The transitivity or ordering of builtin types is known broken in Python 2.7 only between types where equivalency of values of different builtin types is implemented but they are not implemented equivalent to other types.

Example: broken transitivity in IronPython (inspired by Blender's comment and simplified):

>>> assert long(0) < 1.0 < [] < long(0)  # 0 < 1; 'float' < 'list' < 'long'

Even the equivalency (==), that looks simpler to decide, is also not always transitive. That breaks the transitivity of the operator (<=). See examples in comments. (Thanks for fix) (Equivalency is not the identity. a is b implies a == b, but not vice-versa.)

Examples use many trivial user-defined classes, with names one-letter capital or lowercase:

class A(object): pass

class a(object): pass
...

class z(object): pass

Observation - Numbers

All numeric types have many naturally equivalent values in CPython and IronPython (and probably in all other implementations according docs)

>>>  assert (False == 0 == 0L == 0.0 == 0 + 0j == Decimal('0') == Fraction(0, 1) <
...          True == 1 == 1L == 1.0 == 1 + 0j == Decimal('1') == Fraction(1, 1))

Numeric types are ordered before all other types in CPython:

>>> assert 0 < 10**1000 < float('inf') < A() < Z() < a()

Numeric types are dispersed between other types in IronPython

>>> assert D() < decimal.Decimal('0') < E()
>>> assert F() < fractions.Fraction(0, 1) < G()
>>> assert b() < False < c()   # bool
>>> assert c() < 0 + 0j < d()  # complex
>>> assert f() < 0.0 < g()     # float
>>> assert i() < 0 < j()       # int
>>> assert l() < 0L < m()      # long

Strings etc

str, bytearray and unicode have equivalent values

>>> assert bytearray('ab') == 'ab' == u'ab'

Nothing special in ordering to other types is used in CPython,

>>> assert b() < bytearray('ab') < c()  # bytearray
>>> assert s() < 'ab' < t()             # str
>>> assert u() < u'ab' < v()            # unicode in CPython

In IronPython: The type unicode behaves like str. It is not strange because strings are implemented in .NET like unicode and the same is in IronPython.

 >>> assert s() < u'ab' < t()           # unicode in Iron Python like str
 >>> unicode
 <type 'str'>