Made save() fit on a page, while adding comments. (I moved some type
authorGuido van Rossum <guido@python.org>
Tue, 28 Jan 2003 16:34:19 +0000 (16:34 +0000)
committerGuido van Rossum <guido@python.org>
Tue, 28 Jan 2003 16:34:19 +0000 (16:34 +0000)
checks to save_reduce(), which can also be called from a subclass.)

Also tweaked some more comments.

Lib/pickle.py

index ad129889b628826adb616769b156154683c529c0..5a8bfea218facac10bb6ca3246e6d0c45d26534b 100644 (file)
@@ -32,6 +32,7 @@ import marshal
 import sys
 import struct
 import re
+import warnings
 
 __all__ = ["PickleError", "PicklingError", "UnpicklingError", "Pickler",
            "Unpickler", "dump", "dumps", "load", "loads"]
@@ -39,7 +40,7 @@ __all__ = ["PickleError", "PicklingError", "UnpicklingError", "Pickler",
 # These are purely informational; no code usues these
 format_version = "2.0"                  # File format version we write
 compatible_formats = ["1.0",            # Original protocol 0
-                      "1.1",            # Protocol 0 with class supprt added
+                      "1.1",            # Protocol 0 with INST added
                       "1.2",            # Original protocol 1
                       "1.3",            # Protocol 1 with BINFLOAT added
                       "2.0",            # Protocol 2
@@ -249,104 +250,113 @@ class Pickler:
         return GET + `i` + '\n'
 
     def save(self, obj):
+        # Check for persistent id (defined by a subclass)
         pid = self.persistent_id(obj)
-        if pid is not None:
+        if pid:
             self.save_pers(pid)
             return
 
-        memo = self.memo
-        d = id(obj)
-        if d in memo:
-            self.write(self.get(memo[d][0]))
+        # Check the memo
+        x = self.memo.get(id(obj))
+        if x:
+            self.write(self.get(x[0]))
             return
 
+        # Check the type dispatch table
         t = type(obj)
-        try:
-            f = self.dispatch[t]
-        except KeyError:
-            pass
-        else:
-            f(self, obj)
+        f = self.dispatch.get(t)
+        if f:
+            f(self, obj) # Call unbound method with explicit self
             return
 
-        # The dispatch table doesn't know about type t.
+        # Check for a class with a custom metaclass; treat as regular class
         try:
             issc = issubclass(t, TypeType)
-        except TypeError: # t is not a class
+        except TypeError: # t is not a class (old Boost; see SF #502085)
             issc = 0
         if issc:
             self.save_global(obj)
             return
 
-        try:
-            reduce = dispatch_table[t]
-        except KeyError:
-            try:
-                reduce = obj.__reduce__
-            except AttributeError:
-                raise PicklingError, \
-                    "can't pickle %s object: %s" % (`t.__name__`,
-                                                     `obj`)
-            else:
-                tup = reduce()
+        # Check copy_reg.dispatch_table
+        reduce = dispatch_table.get(t)
+        if reduce:
+            rv = reduce(obj)
         else:
-            tup = reduce(obj)
-
-        if type(tup) is StringType:
-            self.save_global(obj, tup)
+            # Check for __reduce__ method
+            reduce = getattr(obj, "__reduce__", None)
+            if not reduce:
+                raise PicklingError("Can't pickle %r object: %r" %
+                                    (t.__name__, obj))
+            rv = reduce()
+
+        # Check for string returned by reduce(), meaning "save as global"
+        if type(rv) is StringType:
+            self.save_global(obj, rv)
             return
 
-        if type(tup) is not TupleType:
-            raise PicklingError, "Value returned by %s must be a " \
-                                 "tuple" % reduce
-
-        l = len(tup)
-
-        if (l != 2) and (l != 3):
-            raise PicklingError, "tuple returned by %s must contain " \
-                                 "only two or three elements" % reduce
+        # Assert that reduce() returned a tuple
+        if type(rv) is not TupleType:
+            raise PicklingError("%s must return string or tuple" % reduce)
 
-        callable = tup[0]
-        arg_tup  = tup[1]
-
-        if l > 2:
-            state = tup[2]
-        else:
+        # Assert that it returned a 2-tuple or 3-tuple, and unpack it
+        l = len(rv)
+        if l == 2:
+            func, args = rv
             state = None
+        elif l == 3:
+            func, args, state = rv
+        else:
+            raise PicklingError("Tuple returned by %s must have "
+                                "exactly two or three elements" % reduce)
 
-        if type(arg_tup) is not TupleType and arg_tup is not None:
-            raise PicklingError, "Second element of tuple returned " \
-                                 "by %s must be a tuple" % reduce
-
-        self.save_reduce(callable, arg_tup, state)
+        # Save the reduce() output and finally memoize the object
+        self.save_reduce(func, args, state)
         self.memoize(obj)
 
     def persistent_id(self, obj):
+        # This exists so a subclass can override it
         return None
 
     def save_pers(self, pid):
+        # Save a persistent id reference
         if self.bin:
             self.save(pid)
             self.write(BINPERSID)
         else:
             self.write(PERSID + str(pid) + '\n')
 
-    def save_reduce(self, acallable, arg_tup, state = None):
-        write = self.write
-        save = self.save
+    def save_reduce(self, func, args, state=None):
+        # This API is be called by some subclasses
 
-        if not callable(acallable):
-            raise PicklingError("__reduce__() must return callable as "
-                                "first argument, not %s" % `acallable`)
+        # Assert that args is a tuple or None
+        if not isinstance(args, TupleType):
+            if args is None:
+                # A hack for Jim Fulton's ExtensionClass, now deprecated.
+                # See load_reduce()
+                warnings.warn("__basicnew__ special case is deprecated",
+                              DeprecationWarning)
+            else:
+                raise PicklingError(
+                    "args from reduce() should be a tuple")
 
-        save(acallable)
-        save(arg_tup)
+        # Assert that func is callable
+        if not callable(func):
+            raise PicklingError("func from reduce should be callable")
+
+        save = self.save
+        write = self.write
+
+        save(func)
+        save(args)
         write(REDUCE)
 
         if state is not None:
             save(state)
             write(BUILD)
 
+    # Methods below this point are dispatched through the dispatch table
+
     dispatch = {}
 
     def save_none(self, obj):
@@ -1028,9 +1038,8 @@ class Unpickler:
                                            "unpickling" % callable
 
         if arg_tup is None:
-            import warnings
-            warnings.warn("The None return argument form of __reduce__  is "
-                          "deprecated. Return a tuple of arguments instead.",
+            # A hack for Jim Fulton's ExtensionClass, now deprecated
+            warnings.warn("__basicnew__ special case is deprecated",
                           DeprecationWarning)
             value = callable.__basicnew__()
         else: