From 8ddc176e2e7ef11b2c46062b29563bc776f177b8 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Mon, 18 Nov 2002 04:34:10 +0000 Subject: [PATCH] Improve DictMixin. Replaced docstring with comments. Prevents subclass contamination. Added the missing __cmp__() method and a test for __cmp__(). Used try/except style in preference to has_key() followed by a look-up. Used iteritem() where possible to save creating a long key list and to save redundant lookups. Expanded .update() to look for the most helpful methods first and gradually work down to a mininum expected interface. Expanded documentation to be more clear on how to use the class. --- Doc/lib/libuserdict.tex | 17 ++++++----- Lib/UserDict.py | 61 ++++++++++++++++++++++++--------------- Lib/test/test_userdict.py | 11 ++++--- 3 files changed, 52 insertions(+), 37 deletions(-) diff --git a/Doc/lib/libuserdict.tex b/Doc/lib/libuserdict.tex index e01c546c75e..ef5c1bf626c 100644 --- a/Doc/lib/libuserdict.tex +++ b/Doc/lib/libuserdict.tex @@ -43,19 +43,20 @@ class. \begin{classdesc}{DictMixin}{} Mixin defining all dictionary methods for classes that already have -a minimum dictionary interface including\method{__getitem__}, -\method{__setitem__}, \method{__delitem__}, and \method{keys}. +a minimum dictionary interface including \method{__getitem__()}, +\method{__setitem__()}, \method{__delitem__()}, and \method{keys()}. This mixin should be used as a superclass. Adding each of the above methods adds progressively more functionality. For instance, -the absence of \method{__delitem__} precludes only \method{pop} -and \method{popitem}. +defining all but \method{__delitem__} will preclude only \method{pop} +and \method{popitem} from the full interface. -While the four methods listed above are sufficient to support the -entire dictionary interface, progessively more efficiency comes -with defining \method{__contains__}, \method{__iter__}, and -\method{iteritems}. +In addition to the four base methods, progessively more efficiency +comes with defining \method{__contains__()}, \method{__iter__()}, and +\method{iteritems()}. +Since the mixin has no knowledge of the subclass constructor, it +does not define \method{__init__()} or \method{copy()}. \end{classdesc} diff --git a/Lib/UserDict.py b/Lib/UserDict.py index b6b36b2066b..96f049dd942 100644 --- a/Lib/UserDict.py +++ b/Lib/UserDict.py @@ -62,13 +62,17 @@ class IterableUserDict(UserDict): return iter(self.data) class DictMixin: - '''Mixin defining all dictionary methods for classes that already have - a minimum dictionary interface including getitem, setitem, delitem, - and keys ''' + # Mixin defining all dictionary methods for classes that already have + # a minimum dictionary interface including getitem, setitem, delitem, + # and keys. Without knowledge of the subclass constructor, the mixin + # does not define __init__() or copy(). In addition to the four base + # methods, progessively more efficiency comes with defining + # __contains__(), __iter__(), and iteritems(). - # first level provided by subclass: getitem, setitem, delitem, and keys - - # second level definitions which assume only getitem and keys + # second level definitions support higher levels + def __iter__(self): + for k in self.keys(): + yield k def has_key(self, key): try: value = self[key] @@ -76,34 +80,30 @@ class DictMixin: return False return True __contains__ = has_key - def __iter__(self): - for k in self.keys(): - yield k - def __len__(self): - return len(self.keys()) - # third level uses second level instead of first + # third level takes advantage of second level definitions def iteritems(self): for k in self: yield (k, self[k]) iterkeys = __iter__ - # fourth level uses second and third levels instead of first + # fourth level uses definitions from lower levels def itervalues(self): for _, v in self.iteritems(): yield v def values(self): - return [self[key] for key in self.keys()] + return [v for _, v in self.iteritems()] def items(self): return list(self.iteritems()) def clear(self): for key in self.keys(): del self[key] def setdefault(self, key, default): - if key not in self: + try: + return self[key] + except KeyError: self[key] = default - return default - return self[key] + return default def pop(self, key): value = self[key] del self[key] @@ -112,15 +112,30 @@ class DictMixin: try: k, v = self.iteritems().next() except StopIteration: - raise KeyError, 'dictionary is empty' + raise KeyError, 'container is empty' del self[k] return (k, v) def update(self, other): - for key in other.keys(): - self[key] = other[key] + # Make progressively weaker assumptions about "other" + if hasattr(other, 'iteritems'): # iteritems saves memory and lookups + for k, v in other.iteritems(): + self[k] = v + elif hasattr(other, '__iter__'): # iter saves memory + for k in other: + self[k] = other[k] + else: + for k in other.keys(): + self[k] = other[k] def get(self, key, default=None): - if key in self: + try: return self[key] - return default + except KeyError: + return default def __repr__(self): - return repr(dict(self.items())) + return repr(dict(self.iteritems())) + def __cmp__(self, other): + if isinstance(other, DictMixin): + other = dict(other.iteritems()) + return cmp(dict(self.iteritems()), other) + def __len__(self): + return len(self.keys()) diff --git a/Lib/test/test_userdict.py b/Lib/test/test_userdict.py index 6f3b853ffcb..a59419d4f91 100644 --- a/Lib/test/test_userdict.py +++ b/Lib/test/test_userdict.py @@ -210,9 +210,8 @@ else: s.update({10: 'ten', 20:'twenty'}) # update verify(s[10]=='ten' and s[20]=='twenty') - - - - - - +verify(s == {10: 'ten', 20:'twenty'}) # cmp +t = SeqDict() +t[20] = 'twenty' +t[10] = 'ten' +verify(s == t)