[Tutor] Sorting Instance Attributes

Danny Yoo dyoo@hkn.eecs.berkeley.edu
Thu Dec 12 13:17:08 2002


> This got me to thinking (which can be dangerous at times): what if you
> don't always want to sort by the same attribute? I played around a
> little, and here's what I came up with:
>
> def setStudentKey(key):
>     """builds the comparison function on the fly"""
>     c = 'def studentCompare(l,r):\n\treturn cmp(str(l.%s),str(r.%s))\n' \
>         % (key, key)
>     exec(c,__builtins__.__dict__)
>
> class Student:
>     def __init__(self,id,firstName,lastName,grade):
>         self.id = id
>         self.firstName = firstName
>         self.lastName = lastName
>         self.grade = grade
>
> if __name__ == '__main__':
>     theClass = []
>     Mary = Student(1,'Mary','Smith', 93)
>     Bob = Student(2,'Bob','Jones', 97)
>     John = Student(3,'John','Albert', 70)
>
>     theClass.append(Bob)
>     theClass.append(John)
>     theClass.append(Mary)
>
>     for currKey in ('id','firstName','lastName','grade'):
>         setStudentKey(currKey)
>         theClass.sort(studentCompare)
>         print 'class sorted by "%s":' % currKey
>         for x in theClass:
>             print '\t%s, %s, %s, %s' % (x.id,x.firstName,x.lastName,x.grade)
>         print "\n"



This is pretty nice!


If we want to avoid using eval, we can still do something similar with
this:

###
def makeStudentComparer(attribute_name):
    def comparison_function(a, b):
        return cmp(getattr(a, attribute_name),
                   getattr(b, attribute_name))
    return comparison_function
###

This function is unusual because it doesn't return a string or a number:
it actually returns a new comparison function that's custom-fitted to
compare what we want.  If we want to mutter in intimidating technical
terms, we'd say makeStudentComparer returns a "closure".  It's actually
not that scary: it's just a function that's dynamically generated, and
that knows about that 'attribute_name' that we originally passed in.


With this, we won't have a single global 'studentCompare()' function, but
we still have much of the power of your original code:


###
     for currKey in ('id','firstName','lastName','grade'):
         theClass.sort(makeStudentComparer(currKey))
         print 'class sorted by "%s":' % currKey
         for x in theClass:
             print '\t%s, %s, %s, %s' % (x.id,x.firstName,x.lastName,x.grade)
         print "\n"
###



Good luck!