Search code examples
pythondictionarysortingcookbook

Recipe from Python Cookbook


I'm trying to implement a recipe from O'Reilly's Python Cookbook (2nd ed.) and a small portion of the recipe doesn't work as it should. I was hoping someone could help me figure out why.

The recipe is "5.14: Enhancing the Dictionary Type with Ratings Functionality." I've got most of the code working, but I get an error when trying to change the 'ranking' of a key. I've left in all the comments as the author's so as anyone trying to help will have a good idea about what the "unders" and "dunders" are for. Especially the "_rating" aspect, which I believe is somehow causing the error.

ERROR:

File "rating.py", line 119, in <module>
    r["john"]=20
  File "rating.py", line 40, in __setitem__
    del self._rating[self.rating(k)]  ##
AttributeError: 'Ratings' object has no attribute 'rating'

CODE:

class Ratings(UserDict.DictMixin, dict):
    '''The implementation carefully mixes inheritance and delegation
    to achieve reasonable performance while minimizing boilerplate,
    and, of course, to ensure semantic correctness as above. All
    mappings' methods not implemented below get inherited, mostly
    from DictMixin, but, crucially!, __getitem__ from dict. '''

    def __init__(self, *args, **kwds):
        ''' This class gets instantiated just like 'dict' '''
        dict.__init__(self, *args, **kwds)
        # self._rating is the crucial auxiliary data structure: a list
        # of all (value, key) pairs, kept in "natural"ly-sorted order
        self._rating = [ (v, k) for k, v in dict.iteritems(self) ]
        self._rating.sort()

    def copy(self):
        ''' Provide an identical but independent copy '''
        return Ratings(self)

    def __setitem__(self, k, v):
        ''' besides delegating to dict, we maintain self._rating '''
        if k in self:
            del self._rating[self.rating(k)]  ##
        dict.__setitem__(self, k, v)
        insort_left(self._rating, (v, k))

    def __delitem__(self, k):
        ''' besides delegating to dict, we maintain self._rating '''
        del self._rating[self.rating(k)]
        dict.__delitem__(self, k)
        ''' delegate some methods to dict explicitly to avoid getting
        DictMixin's slower (though correct) implementations instead '''
        __len__ = dict.__len__
        __contains__ = dict.__contains__
        has_key = __contains__
        ''' the key semantic connection between self._rating and the order
        of self.keys( ) -- DictMixin gives us all other methods 'for
        free', although we could implement them directly for slightly
        better performance. '''

        def __iter__(self):
            for v, k in self._rating:
                yield k
            iterkeys = __iter__

        def keys(self):
            return list(self)

        #the three ratings-related methods
        def rating(self, key):
            item = self[key], key
            i = bisect_left(self._rating, item)
            if item == self._rating[i]:
                return i
            raise LookUpError, "item not found in rating"

        def getValueByRating(self, rating):
            return self._rating[rating][0]

        def getKeyByRating(self, rating):
            return self._rating[rating][1]

        def _test( ):
            ''' we use doctest to test this module, which must be named
            rating.py, by validating all the examples in docstrings. '''
            import doctest, rating
            doctest.testmod(rating)



if __name__ == "__main__":

    r = Ratings({"bob":30, "john":30})

    print "r is"
    print r
    print "\n"
    print "len(r) is"
    print len(r)
    print "\n"
    print "updating with {'paul': 20, 'tom': 10} "
    r.update({"paul": 20, "tom": 10})
    print "\n"
    print "now r is"
    print r
    print "\n"
    print "r.has_key('paul') is"
    print r.has_key("paul")
    print "\n"
    print " 'paul' in r is"
    print ("paul" in r)
    print "\n"
    print "r.has_key('alex') is"
    print r.has_key("alex")
    print "\n"
    print " 'alex' in r is"
    print ("alex" in r)
    print '\n'
    print 'r is'
    print r
    print "changing john to '20' with 'r['john']= 20' doesn't work. "
    r["john"]=20

Solution

  • Your issue is with indentation. You would have to move the following block one level to the left for them to be recognized as class methods.

        def __iter__(self):
            for v, k in self._rating:
                yield k
            iterkeys = __iter__
    
        def keys(self):
            return list(self)
    
        #the three ratings-related methods
        def rating(self, key):
            item = self[key], key
            i = bisect_left(self._rating, item)
            if item == self._rating[i]:
                return i
            raise LookUpError, "item not found in rating"
    
        def getValueByRating(self, rating):
            return self._rating[rating][0]
    
        def getKeyByRating(self, rating):
            return self._rating[rating][1]
    
        def _test( ):
            ''' we use doctest to test this module, which must be named
            rating.py, by validating all the examples in docstrings. '''
            import doctest, rating
            doctest.testmod(rating)
            print "doc test?"
    

    So the class would be

    class Ratings(UserDict.DictMixin, dict):
        '''The implementation carefully mixes inheritance and delegation
        to achieve reasonable performance while minimizing boilerplate,
        and, of course, to ensure semantic correctness as above. All
        mappings' methods not implemented below get inherited, mostly
        from DictMixin, but, crucially!, __getitem__ from dict. '''
    
        def __init__(self, *args, **kwds):
            ''' This class gets instantiated just like 'dict' '''
            dict.__init__(self, *args, **kwds)
            # self._rating is the crucial auxiliary data structure: a list
            # of all (value, key) pairs, kept in "natural"ly-sorted order
            self._rating = [ (v, k) for k, v in dict.iteritems(self) ]
            self._rating.sort()
    
        def copy(self):
            ''' Provide an identical but independent copy '''
            return Ratings(self)
    
        def __setitem__(self, k, v):
            ''' besides delegating to dict, we maintain self._rating '''
            if k in self:
                del self._rating[self.rating(k)]  ##
            dict.__setitem__(self, k, v)
            insort_left(self._rating, (v, k))
    
        def __delitem__(self, k):
            ''' besides delegating to dict, we maintain self._rating '''
            del self._rating[self.rating(k)]
            dict.__delitem__(self, k)
            ''' delegate some methods to dict explicitly to avoid getting
            DictMixin's slower (though correct) implementations instead '''
            __len__ = dict.__len__
            __contains__ = dict.__contains__
            has_key = __contains__
            ''' the key semantic connection between self._rating and the order
            of self.keys( ) -- DictMixin gives us all other methods 'for
            free', although we could implement them directly for slightly
            better performance. '''
    
        def __iter__(self):
            for v, k in self._rating:
                yield k
            iterkeys = __iter__
    
        def keys(self):
            return list(self)
    
        #the three ratings-related methods
        def rating(self, key):
            item = self[key], key
            i = bisect_left(self._rating, item)
            if item == self._rating[i]:
                return i
            raise LookUpError, "item not found in rating"
    
        def getValueByRating(self, rating):
            return self._rating[rating][0]
    
        def getKeyByRating(self, rating):
            return self._rating[rating][1]
    
        def _test( ):
            ''' we use doctest to test this module, which must be named
            rating.py, by validating all the examples in docstrings. '''
            import doctest, rating
            doctest.testmod(rating)
            print "doc test?"
    
    if __name__ == "__main__":
    
        r = Ratings({"bob":30, "john":30})
    
        print "r is"
        print r
        print "\n"
        print "len(r) is"
        print len(r)
        print "\n"
        print "updating with {'paul': 20, 'tom': 10} "
        r.update({"paul": 20, "tom": 10})
        print "\n"
        print "now r is"
        print r
        print "\n"
        print "r.has_key('paul') is"
        print r.has_key("paul")
        print "\n"
        print " 'paul' in r is"
        print ("paul" in r)
        print "\n"
        print "r.has_key('alex') is"
        print r.has_key("alex")
        print "\n"
        print " 'alex' in r is"
        print ("alex" in r)
        print '\n'
        print 'r is'
        print r
        print "changing john to '20' with 'r['john']= 20' doesn't work. "
        r["john"]=20