[Python-Dev] Coercion and comparisons

Ka-Ping Yee ping@lfw.org
Tue, 6 Feb 2001 11:00:02 -0800 (PST)


On Tue, 6 Feb 2001, Fredrik Lundh wrote:
> 
> can this be fixed?  should this be fixed?  (please?)

I'm not sure.  The gmpy example:

> > >>> a = [mpz(1),[]]
> > >>> a.index([])
> > Traceback (most recent call last):
> >   File "<stdin>", line 1, in ?
> > TypeError: coercion to gmpy.mpz type failed

seems to be just one case of coercion failure.  I no longer
have Python 2.0 in a state on my machine where i can compile
gmpy to test with it, but you can perform the same exercise
with the mpz module in 2.1a2:

    >>> import mpz
    >>> [mpz.mpz(1), []].index([]) 
    Traceback (most recent call last):
      File "<stdin>", line 1, in ?
    TypeError: number coercion (to mpzobject) failed

The following test shows that the issue is present for Python
classes too:

    >>> class Foo:
    ...     def __coerce__(self, other):
    ...         raise TypeError, 'coercion failed'
    ... 
    >>> f = Foo()
    >>> s = [3, f, 5]
    >>> s.index(3)
    0
    >>> s.index(5)
    Traceback (most recent call last):
      File "<stdin>", line 1, in ?
      File "<stdin>", line 3, in __coerce__
    TypeError: coercion failed

I get the above behaviour in 1.5.2, 2.0, and 2.1a2.

So now we have to ask whether index() should hide these errors.
It seems to me that conventional Python philosophy would argue
to let the errors flaunt themselves as early as possible, but
i agree with you that the failure to find [] in [mpz(1), []] is
pretty jarring.

??

Hmm, i think perhaps the right answer is to not coerce before ==,
even if we automatically coerce before the other comparison operators.

But, this is only good as a future possibility.  It can't resolve
the issue for existing extension modules because their old-style
comparison functions appear to expect two arguments of the same type:

    (in mpzmodule.c)

    static int
    mpz_compare(mpzobject *a, mpzobject *b)
    {
        int cmpres;

        /* guido sez it's better to return -1, 0 or 1 */
        return (cmpres = mpz_cmp( &a->mpz, &b->mpz )) == 0 ? 0
               : cmpres > 0 ? 1 : -1;
    } /* mpz_compare() */

...so the error occurs before tp_compare has a chance to say
"okay, it's not equal".  We have to ask the authors of extension
modules to implement == separately from the other comparisons.

Note, by the way, that this re-raises the matter of the three
kinds of equality that i remember mentioning back when we were
discussing rich comparisons.  I'll restate them here for you to
think about.

The three kinds of equality (in order by strength) are:

    1.  Identity.       Python: 'x is y'       E: 'x == y'
                        Python: 'x is not y'   E: 'x != y'
        Meaning: "x and y are the same object.  Substituting x for y
        in any computation makes no difference to the result."

    2.  Value.          Python: 'x == y'       E: 'x.equals(y)'
                        Python: 'x != y'       E: '!x.equals(y)'
        Meaning: "x and y represent the same value.  Substituting x
        for y in any operation that doesn't mutate x or y yields
        results that are ==."

    3.  Magnitude.      Python: missing        E: 'x <=> y'
                        Python: missing        E: 'x <> y'
        Meaning: "x and y have the same size.  Another way to say
        this is that both x <= y and x >= y are true."

Same identity implies same value; same value implies same magnitude.

    Category        Python operators        E operators

    identity        is, is not              ==, !=
    value           ==, !=, <>              x.equals(y), !x.equals(y)
    magnitude       <, <=, >, >=            <, <=, >, >=, <>, <=>

Each type of equality has a specific and useful meaning.  Most
languages, including Python, acknowledge the first two.  But you
can see how the coercion problem raised above is a consequence
of the fact that the third category is incomplete.

I like Python's spelling better than E's, though it's a small wart
that there is no easy way to say or implement 'same magnitude'.
(You can get around it by saying 'x <= y <= x', i suppose, but
there's no real interface on the C side.)


-- ?!ng

"There's no point in being grown up if you can't be childish sometimes."
    -- Dr. Who