[Tutor] __cmp__() method examples?

Kirby Urner urnerk@qwest.net
Wed, 05 Dec 2001 21:39:29 -0800


At 09:35 PM 12/5/2001 -0600, Rob McGee wrote:
>I haven't found many examples of how to do a Class.__cmp__() method. I
>have a class whose instances have a certain alphabetic value which
>should always be unique. I figured I would only be comparing these class
>instances to other instances of the same class, so I chose to compare
>based on that alphabetic value.
>
>Maybe an example will clarify:
>
>{code}
>import random
>
>class Project:
>   def __init__(self, name):
>     self.name = name
>     self.value = random.randrange(1, 100)
>   def __str__(self):
>     return self.name
>   def __cmp__(self, other):
>     if self.name < other.name:  # compare name value (should be unique)
>       return -1
>     elif self.name > other.name:
>       return 1
>     else: return 0              # should mean it's the same instance
>
># now generate class instances -- this is always done automatically
>from string import uppercase
>for letter in uppercase:
>   execStr = letter + ' = Project("' + letter + '")'
>   # example -- 'A = Project("A")', 'Z = Project("Z")
>   exec(execStr)
>
>{/code}
>
>(Sorry about the exec's, but I really don't know another way to do what
>I want. I want these instances in the global namespace, and I need to
>know exactly what each one is named. At least I think I do. Suggestions
>about other ways to approach this would be welcomed, too. :)

Hey, s'ok, really.  It's just that some people who want to help
might find this exec piece hard to fathom, so you cut down on
the number willing to look over your shoulder.  Or at least so
I hypothesize.  My suggestion earlier was, even if YOU need to
automatically assign, for the purposes of debugging here on
the list, you could just as well show (as you did in the
Example):

    >>>  A = Project("A")
    >>>  Z = Project("Z")

Of course I have approximately zero understanding of what
you're doing in your off-camera code, but another way to
instantiate a lot of objects and keep them handy is to use
a list, e.g.

    myobjects = []
    for i in range(10):
        myobjects.append(Project())

this would create 10 Project objects in a list called
myobjects, addressable by indexing.

Another approach, if you want to refer to objects by name,
is to use a dictionary, e.g.:

    myobjects = {}
    for name in ['A','B','C','D','E']:
       myobjects[name] = Project()

I don't even bother to give Projects a unique identifier for
internal storage, because I only access 'em through my list
or dictionary, and I know they're different if the index
and/or dictionary key is different.

>Like with
>no exec's, or with the instances in something other than
>globals(), how would I be able to retrieve and alter their
>properties?)

myobjects['A'].property = "new value"

or:

      for each in myobjects:
           myobjects[each].method(arg1,arg2)

E.g.

   >>> myobjects = {}
   >>> myobjects['A']= Project('Kirby')
   >>> myobjects['A'].name
   'Kirby'
   >>> myobjects['A'].name = 'Sally'
   >>> myobjects['A'].name
   'Sally'

This might not look like a big improvement, but on the
bright side, you have all your objects in an "egg carton"
(the myobjects dictionary) and if you want to iterate
through 'em all at any point, this makes it easy.

Just some options you might not have considered.  May not
be applicable.

>The user interacts with these class instances through code like this:
>
>{code}
>def getProjectName():
>   text = raw_input('Project name: ')
>   text = text.upper()
>   return text
>
>def objectEntry(text):
>   """converts text to an object name"""
>   try:
>     object = eval(text)
>     return object
>   except: return

def getobj():
    global myobjects
    objname = raw_input('Project name: ')
    if objname in myobjects.keys():
        return myobjects['objname']
    else:
        return None

>I'm getting errors which indicate that the class instances are being
>compared with other objects (AttributeError: object has no "name"
>attribute.) I don't know if the sample code here will generate those
>errors -- probably not -- but what I'm hoping for is just a little
>guidance on how to properly implement a __cmp__() method.

Yeah, unfortunately, the code you posted has no problems.  I
cut and pasted your class, imported random, and tested it:

  >>> import random
  >>> A = Project('A')
  >>> B = Project('B')
  >>> A==B  # note we're using ==, not =, which would *assign*
            # B to A, thereby screwing us up
  0
  >>> C = Project('A')  # give same name to other object
  >>> C==A
  1

Note that we're returning 1 because that's what we defined
__cmp__ to do.  We say "return 1 if the names are the same".
This does NOT mean that C and A are really the same
instance, i.e. are the same chunk of bytes in memory.
Of course they *aren't*:

   >>> id(A)
   11557216
   >>> id(C)
   11500800

>What I think I'll try -- the idea came to me while writing this :) -- is
>to put code in Project.__cmp__() like the ifInstance() above. If "other"
>isn't an instance of self.__class__, don't try to get other.name.


Yeah, you could do things like that, but you need to get a
better handle on why it's not working in the first place.
Bug fixes of the type "I don't know why this was breaking
but here's a fix", are convenient, but as the patchwork
grows, the code turns opaque, even to the only programmer
with a hope of keeping a grip on it.


>Oh, I should also mention that I got into an infinite loop with another
>class.__cmp__() method. That class was more complicated, and I had
>several if/elif loops to compare other attributes. The fourth and final
>such loop simply compared "self" and "other". I believe that was where
>the loop occurred, and it was in sorting a list of instances of that
>class (I don't think there were any non-instance members of the list.)

Try thinking of using some other data structure, like a list
or dictionary, as per above, to keep objects separate, each
with a unique pointer.  You usually shouldn't have to do
elaborate testing to figure out whether an object is the same
as some other object.

>By removing the self/other raw comparison I eliminated the infinite
>loop. But that experience gives me the idea that I'm missing something
>about how to do a Class.__cmp__().
>
>Thanks again all,
>
>     Rob - /dev/rob0

Note that in recent Pythons you don't have to go through __cmp__
to test for equality, if that's really all you're interested
in (not saying it is).  I.e., you could go:

    class Project:
       def __init__(self, name):
          self.name = name
          self.value = random.randrange(1, 100)
       def __repr__(self):
          return self.name
       def __eq__(self, other):
          if self.name == other.name:
             return 1
          else:  return 0

  >>> A = Project('A')
  >>> J = Project('J')
  >>> A==J
  0
  >>> R = Project('J')
  >>> A==R
  0
  >>> J==R
  1

Kirby