diff --git a/advanced-classes.html b/advanced-classes.html index a931634..782bf58 100644 --- a/advanced-classes.html +++ b/advanced-classes.html @@ -27,49 +27,92 @@ body{counter-reset:h1 11}

[FIXME here's why ordered dicts are useful: http://www.gossamer-threads.com/lists/python/dev/656556 ]

[download ordereddict.py] -

import collections
-import itertools
- 
-class OrderedDict(dict, collections.MutableMapping):
+
class OrderedDict(dict, MutableMapping):
+    'Dictionary that remembers insertion order'
+    # An inherited dict maps keys to values.
+    # The inherited dict provides __getitem__, __len__, __contains__, and get.
+    # The remaining methods are order-aware.
+    # Big-O running times for all methods are the same as for regular dictionaries.
+
+    # The internal self.__map dictionary maps keys to links in a doubly linked list.
+    # The circular doubly linked list starts and ends with a sentinel element.
+    # The sentinel element never gets deleted (this simplifies the algorithm).
+    # The prev/next links are weakref proxies (to prevent circular references).
+    # Individual links are kept alive by the hard reference in self.__map.
+    # Those hard references disappear when a key is deleted from an OrderedDict.
 
     def __init__(self, *args, **kwds):
+        '''Initialize an ordered dictionary.  Signature is the same as for
+        regular dictionaries, but keyword arguments are not recommended
+        because their insertion order is arbitrary.
+
+        '''
         if len(args) > 1:
-            raise TypeError('expected at most 1 arguments')
-        if not hasattr(self, '_keys'):
-            self._keys = []
+            raise TypeError('expected at most 1 arguments, got %d' % len(args))
+        try:
+            self.__root
+        except AttributeError:
+            self.__root = root = _Link()    # sentinel node for the doubly linked list
+            root.prev = root.next = root
+            self.__map = {}
         self.update(*args, **kwds)
 
     def clear(self):
-        del self._keys[:]
+        'od.clear() -> None.  Remove all items from od.'
+        root = self.__root
+        root.prev = root.next = root
+        self.__map.clear()
         dict.clear(self)
 
     def __setitem__(self, key, value):
+        'od.__setitem__(i, y) <==> od[i]=y'
+        # Setting a new item creates a new link which goes at the end of the linked
+        # list, and the inherited dictionary is updated with the new key/value pair.
         if key not in self:
-            self._keys.append(key)
+            self.__map[key] = link = _Link()
+            root = self.__root
+            last = root.prev
+            link.prev, link.next, link.key = last, root, key
+            last.next = root.prev = _proxy(link)
         dict.__setitem__(self, key, value)
 
     def __delitem__(self, key):
+        'od.__delitem__(y) <==> del od[y]'
+        # Deleting an existing item uses self.__map to find the link which is
+        # then removed by updating the links in the predecessor and successor nodes.
         dict.__delitem__(self, key)
-        self._keys.remove(key)
+        link = self.__map.pop(key)
+        link.prev.next = link.next
+        link.next.prev = link.prev
 
     def __iter__(self):
-        return iter(self._keys)
+        'od.__iter__() <==> iter(od)'
+        # Traverse the linked list in order.
+        root = self.__root
+        curr = root.next
+        while curr is not root:
+            yield curr.key
+            curr = curr.next
 
     def __reversed__(self):
-        return reversed(self._keys)
-
-    def popitem(self):
-        if not self:
-            raise KeyError('dictionary is empty')
-        key = self._keys.pop()
-        value = dict.pop(self, key)
-        return key, value
+        'od.__reversed__() <==> reversed(od)'
+        # Traverse the linked list in reverse order.
+        root = self.__root
+        curr = root.prev
+        while curr is not root:
+            yield curr.key
+            curr = curr.prev
 
     def __reduce__(self):
+        'Return state information for pickling'
         items = [[k, self[k]] for k in self]
+        tmp = self.__map, self.__root
+        del self.__map, self.__root
         inst_dict = vars(self).copy()
-        inst_dict.pop('_keys', None)
-        return (self.__class__, (items,), inst_dict)
+        self.__map, self.__root = tmp
+        if inst_dict:
+            return (self.__class__, (items,), inst_dict)
+        return self.__class__, (items,)
 
     setdefault = MutableMapping.setdefault
     update = MutableMapping.update
@@ -78,25 +121,54 @@ class OrderedDict(dict, collections.MutableMapping):
     values = MutableMapping.values
     items = MutableMapping.items
 
-    def __repr__(self):
+    def popitem(self, last=True):
+        '''od.popitem() -> (k, v), return and remove a (key, value) pair.
+        Pairs are returned in LIFO order if last is true or FIFO order if false.
+
+        '''
         if not self:
-            return '{0}()'.format(self.__class__.__name__,)
-        return '{0}({1})'.format(self.__class__.__name__, repr(list(self.items())))
+            raise KeyError('dictionary is empty')
+        key = next(reversed(self) if last else iter(self))
+        value = self.pop(key)
+        return key, value
+
+    def __repr__(self):
+        'od.__repr__() <==> repr(od)'
+        if not self:
+            return '%s()' % (self.__class__.__name__,)
+        return '%s(%r)' % (self.__class__.__name__, list(self.items()))
 
     def copy(self):
+        'od.copy() -> a shallow copy of od'
         return self.__class__(self)
 
     @classmethod
     def fromkeys(cls, iterable, value=None):
+        '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S
+        and values equal to v (which defaults to None).
+
+        '''
         d = cls()
         for key in iterable:
             d[key] = value
         return d
 
     def __eq__(self, other):
+        '''od.__eq__(y) <==> od==y.  Comparison to another OD is order-sensitive
+        while comparison to a regular mapping is order-insensitive.
+
+        '''
         if isinstance(other, OrderedDict):
-            return all(p==q for p, q in itertools.zip_longest(self.items(), other.items()))
-        return dict.__eq__(self, other)
+ return len(self)==len(other) and \ + all(p==q for p, q in zip(self.items(), other.items())) + return dict.__eq__(self, other) + + def __ne__(self, other): + '''od.__ne__(y) <==> od!=y. Comparison to another OD is order-sensitive + while comparison to a regular mapping is order-insensitive. + + ''' + return not self == other

⁂ diff --git a/layout.css b/layout.css new file mode 100644 index 0000000..1c88c70 --- /dev/null +++ b/layout.css @@ -0,0 +1,74 @@ +/* + +"Dive Into Python 3" layout stylesheet + +Copyright (c) 2009, Mark Pilgrim, All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ + +@page { + size: US-Letter; + margin: 1.75in; + padding: 0; + @bottom-center { + font: 12pt/1.75 serif; + content: counter(page); + } +} +body, .w a { + font: 10pt/1.3 serif; +} +pre, kbd, samp, code, var, .b { + font: 8pt/1.3 monospace; +} +span { + font-size: 10pt; +} +.baa { + font-size: 11pt; +} +.q span { + font-size: 13pt; +} +.f:first-letter { + color: #888; + font: normal 48pt/0.68 serif; +} +p, ul, ol { + margin: 0; + font-size: 11pt; +} +p + p { + text-indent: 1em; +} + +h1 { + page-break-before: always; + prince-bookmark-level: 1; +} +h2 { + prince-bookmark-level: 2; +} +h3 { + prince-bookmark-level: 3; +} diff --git a/porting-code-to-python-3-with-2to3.html b/porting-code-to-python-3-with-2to3.html index cba5f0f..d48fc42 100644 --- a/porting-code-to-python-3-with-2to3.html +++ b/porting-code-to-python-3-with-2to3.html @@ -288,7 +288,7 @@ import whichdb Python 2 Python 3 -import xmlrpclib +import xmlrpclib import xmlrpc.client

import DocXMLRPCServer
diff --git a/util/flatten.py b/util/flatten.py
index 413d8a7..3f3bc8f 100644
--- a/util/flatten.py
+++ b/util/flatten.py
@@ -44,7 +44,7 @@ for filename in chapters:
     for line in open(filename, encoding="utf-8"):
         if line.count('

'): include = True - if line.count('

©'): include = False if line.count('