[Python-bugs-list] [Bug #125989] cmp() broken on instances

noreply@sourceforge.net noreply@sourceforge.net
Sat, 16 Dec 2000 11:14:20 -0800


Bug #125989, was updated on 2000-Dec-16 09:22
Here is a current snapshot of the bug.

Project: Python
Category: Python Interpreter Core
Status: Closed
Resolution: Invalid
Bug Group: Not a Bug
Priority: 3
Submitted by: nascheme
Assigned to : tim_one
Summary: cmp() broken on instances

Details: I found this while working on the implementation for PEP 208. Python
1.5.2 and Python 2.0 both give the same behavior (tested on Linux and
Solaris).

class CoerceNumber:
    def __init__(self, arg):
        self.arg = arg

    def __coerce__(self, other):
        if isinstance(other, CoerceNumber):
            return self.arg, other.arg
        else:
            return self.arg, other

class MethodNumber:
    def __init__(self,arg):
	self.arg = arg

    def __cmp__(self, other):
        return cmp(self.arg, other)

# the order of instantiation matters!
m = MethodNumber(1)
c = CoerceNumber(2)
print "cmp(<MethodNumber 1>,<CoerceNumber 2>) =", cmp(m, c)
print "cmp(<CoerceNumber 2>,<MethodNumber 1>) =", cmp(c, m)


Randomly assigned to Tim.  I don't have time to figure out what's
happening right now.  Tim, feel free to ignore.  It should be fixed by PEP
208 anyhow.

Follow-Ups:

Date: 2000-Dec-16 11:14
By: tim_one

Comment:
My belief:  the behavior of cmp(m, c) is well-defined, but the behavior of cmp(c, m) is not because the __coerce__ function in that case breaks the rules.  Here's an excruciating explanation, referring to the Lang Ref's coercion rules at the bottom of:

http://www.python.org/doc/current/ref/numeric-types.html

cmp(m, c)
m is an instance that doesn't define __coerce__ but does define __cmp__, so (rule 1c) m.__cmp__(c) is evaluated.
That in turn evaluates cmp(m.arg, c) == cmp(1, c).
By rule 2a, in cmp(1, c) the arguments are replaced by the
(swapped!) result of c.__coerce__(1) == the swap of (c.arg, 1) == the swap of (2, 1) == (1, 2).
cmp(1, 2) then returns -1, by rule 3c.

cmp(c, m)
By rule 1a, the args are replaced by c.__coerce__(m) ==
(c.arg, m) = (2, m).  Note that this breaks the rules:
__coerce__ is *supposed* to coerce to a common type, or return None.  It did neither here.
Pushing on anyway, 2 does not have a method __cmp__, so by
rule 1c we ignore everything we've done and go on to step 2,
but starting over with args (c, m) again.
None of the rules in step 2 apply (m doesn't have a __coerce__ or an __rcmp__), so we fall into step 3.
Now "we only get here if neither x nor y is a class instance" is false, because c.__coerce__ broke the rules.  I suspect that what happens then is that the objects get compared by storage address, perhaps by the "/* Sigh -- special case for comparisons */" code in PyInstance_DoBinOp.  And that's why the result of cmp(c, m) varies between -1 and +1, but is never 0.

If that's all correct, then the code is functioning as documented, so there's no bug.  You could argue there's a design error -- although there are so many steps to consider it's hard to say exactly where <wink>.

-------------------------------------------------------

For detailed info, follow this link:
http://sourceforge.net/bugs/?func=detailbug&bug_id=125989&group_id=5470