summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/pickle.py129
1 files changed, 69 insertions, 60 deletions
diff --git a/Lib/pickle.py b/Lib/pickle.py
index ad12988..5a8bfea 100644
--- a/Lib/pickle.py
+++ b/Lib/pickle.py
@@ -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: