Commit d9dfaa94 authored by Antoine Pitrou's avatar Antoine Pitrou

Issue #6137: The pickle module now translates module names when loading

or dumping pickles with a 2.x-compatible protocol, in order to make data
sharing and migration easier. This behaviour can be disabled using the
new `fix_imports` optional argument.
parent 751899a5
...@@ -141,7 +141,7 @@ an unpickler, then you call the unpickler's :meth:`load` method. The ...@@ -141,7 +141,7 @@ an unpickler, then you call the unpickler's :meth:`load` method. The
The :mod:`pickle` module provides the following functions to make the pickling The :mod:`pickle` module provides the following functions to make the pickling
process more convenient: process more convenient:
.. function:: dump(obj, file[, protocol]) .. function:: dump(obj, file[, protocol, \*, fix_imports=True])
Write a pickled representation of *obj* to the open file object *file*. This Write a pickled representation of *obj* to the open file object *file*. This
is equivalent to ``Pickler(file, protocol).dump(obj)``. is equivalent to ``Pickler(file, protocol).dump(obj)``.
...@@ -158,7 +158,11 @@ process more convenient: ...@@ -158,7 +158,11 @@ process more convenient:
argument. It can thus be a file object opened for binary writing, a argument. It can thus be a file object opened for binary writing, a
io.BytesIO instance, or any other custom object that meets this interface. io.BytesIO instance, or any other custom object that meets this interface.
.. function:: dumps(obj[, protocol]) If *fix_imports* is True and *protocol* is less than 3, pickle will try to
map the new Python 3.x names to the old module names used in Python 2.x,
so that the pickle data stream is readable with Python 2.x.
.. function:: dumps(obj[, protocol, \*, fix_imports=True])
Return the pickled representation of the object as a :class:`bytes` Return the pickled representation of the object as a :class:`bytes`
object, instead of writing it to a file. object, instead of writing it to a file.
...@@ -171,7 +175,11 @@ process more convenient: ...@@ -171,7 +175,11 @@ process more convenient:
supported. The higher the protocol used, the more recent the version of supported. The higher the protocol used, the more recent the version of
Python needed to read the pickle produced. Python needed to read the pickle produced.
.. function:: load(file, [\*, encoding="ASCII", errors="strict"]) If *fix_imports* is True and *protocol* is less than 3, pickle will try to
map the new Python 3.x names to the old module names used in Python 2.x,
so that the pickle data stream is readable with Python 2.x.
.. function:: load(file, [\*, fix_imports=True, encoding="ASCII", errors="strict"])
Read a pickled object representation from the open file object *file* and Read a pickled object representation from the open file object *file* and
return the reconstituted object hierarchy specified therein. This is return the reconstituted object hierarchy specified therein. This is
...@@ -187,11 +195,14 @@ process more convenient: ...@@ -187,11 +195,14 @@ process more convenient:
for reading, a BytesIO object, or any other custom object that meets this for reading, a BytesIO object, or any other custom object that meets this
interface. interface.
Optional keyword arguments are encoding and errors, which are used to decode Optional keyword arguments are *fix_imports*, *encoding* and *errors*,
8-bit string instances pickled by Python 2.x. These default to 'ASCII' and which are used to control compatiblity support for pickle stream generated
'strict', respectively. by Python 2.x. If *fix_imports* is True, pickle will try to map the old
Python 2.x names to the new names used in Python 3.x. The *encoding* and
*errors* tell pickle how to decode 8-bit string instances pickled by Python
2.x; these default to 'ASCII' and 'strict', respectively.
.. function:: loads(bytes_object, [\*, encoding="ASCII", errors="strict"]) .. function:: loads(bytes_object, [\*, fix_imports=True, encoding="ASCII", errors="strict"])
Read a pickled object hierarchy from a :class:`bytes` object and return the Read a pickled object hierarchy from a :class:`bytes` object and return the
reconstituted object hierarchy specified therein reconstituted object hierarchy specified therein
...@@ -200,9 +211,12 @@ process more convenient: ...@@ -200,9 +211,12 @@ process more convenient:
argument is needed. Bytes past the pickled object's representation are argument is needed. Bytes past the pickled object's representation are
ignored. ignored.
Optional keyword arguments are encoding and errors, which are used to decode Optional keyword arguments are *fix_imports*, *encoding* and *errors*,
8-bit string instances pickled by Python 2.x. These default to 'ASCII' and which are used to control compatiblity support for pickle stream generated
'strict', respectively. by Python 2.x. If *fix_imports* is True, pickle will try to map the old
Python 2.x names to the new names used in Python 3.x. The *encoding* and
*errors* tell pickle how to decode 8-bit string instances pickled by Python
2.x; these default to 'ASCII' and 'strict', respectively.
The :mod:`pickle` module defines three exceptions: The :mod:`pickle` module defines three exceptions:
...@@ -233,7 +247,7 @@ The :mod:`pickle` module defines three exceptions: ...@@ -233,7 +247,7 @@ The :mod:`pickle` module defines three exceptions:
The :mod:`pickle` module exports two classes, :class:`Pickler` and The :mod:`pickle` module exports two classes, :class:`Pickler` and
:class:`Unpickler`: :class:`Unpickler`:
.. class:: Pickler(file[, protocol]) .. class:: Pickler(file[, protocol, \*, fix_imports=True])
This takes a binary file for writing a pickle data stream. This takes a binary file for writing a pickle data stream.
...@@ -249,6 +263,10 @@ The :mod:`pickle` module exports two classes, :class:`Pickler` and ...@@ -249,6 +263,10 @@ The :mod:`pickle` module exports two classes, :class:`Pickler` and
argument. It can thus be a file object opened for binary writing, a argument. It can thus be a file object opened for binary writing, a
io.BytesIO instance, or any other custom object that meets this interface. io.BytesIO instance, or any other custom object that meets this interface.
If *fix_imports* is True and *protocol* is less than 3, pickle will try to
map the new Python 3.x names to the old module names used in Python 2.x,
so that the pickle data stream is readable with Python 2.x.
.. method:: dump(obj) .. method:: dump(obj)
Write a pickled representation of *obj* to the open file object given in Write a pickled representation of *obj* to the open file object given in
...@@ -277,7 +295,7 @@ The :mod:`pickle` module exports two classes, :class:`Pickler` and ...@@ -277,7 +295,7 @@ The :mod:`pickle` module exports two classes, :class:`Pickler` and
Use :func:`pickletools.optimize` if you need more compact pickles. Use :func:`pickletools.optimize` if you need more compact pickles.
.. class:: Unpickler(file, [\*, encoding="ASCII", errors="strict"]) .. class:: Unpickler(file, [\*, fix_imports=True, encoding="ASCII", errors="strict"])
This takes a binary file for reading a pickle data stream. This takes a binary file for reading a pickle data stream.
...@@ -290,9 +308,12 @@ The :mod:`pickle` module exports two classes, :class:`Pickler` and ...@@ -290,9 +308,12 @@ The :mod:`pickle` module exports two classes, :class:`Pickler` and
for reading, a BytesIO object, or any other custom object that meets this for reading, a BytesIO object, or any other custom object that meets this
interface. interface.
Optional keyword arguments are encoding and errors, which are used to decode Optional keyword arguments are *fix_imports*, *encoding* and *errors*,
8-bit string instances pickled by Python 2.x. These default to 'ASCII' and which are used to control compatiblity support for pickle stream generated
'strict', respectively. by Python 2.x. If *fix_imports* is True, pickle will try to map the old
Python 2.x names to the new names used in Python 3.x. The *encoding* and
*errors* tell pickle how to decode 8-bit string instances pickled by Python
2.x; these default to 'ASCII' and 'strict', respectively.
.. method:: load() .. method:: load()
......
# This module is used to map the old Python 2 names to the new names used in
# Python 3 for the pickle module. This needed to make pickle streams
# generated with Python 2 loadable by Python 3.
# This is a copy of lib2to3.fixes.fix_imports.MAPPING. We cannot import
# lib2to3 and use the mapping defined there, because lib2to3 uses pickle.
# Thus, this could cause the module to be imported recursively.
IMPORT_MAPPING = {
'StringIO': 'io',
'cStringIO': 'io',
'cPickle': 'pickle',
'__builtin__' : 'builtins',
'copy_reg': 'copyreg',
'Queue': 'queue',
'SocketServer': 'socketserver',
'ConfigParser': 'configparser',
'repr': 'reprlib',
'FileDialog': 'tkinter.filedialog',
'tkFileDialog': 'tkinter.filedialog',
'SimpleDialog': 'tkinter.simpledialog',
'tkSimpleDialog': 'tkinter.simpledialog',
'tkColorChooser': 'tkinter.colorchooser',
'tkCommonDialog': 'tkinter.commondialog',
'Dialog': 'tkinter.dialog',
'Tkdnd': 'tkinter.dnd',
'tkFont': 'tkinter.font',
'tkMessageBox': 'tkinter.messagebox',
'ScrolledText': 'tkinter.scrolledtext',
'Tkconstants': 'tkinter.constants',
'Tix': 'tkinter.tix',
'ttk': 'tkinter.ttk',
'Tkinter': 'tkinter',
'markupbase': '_markupbase',
'_winreg': 'winreg',
'thread': '_thread',
'dummy_thread': '_dummy_thread',
'dbhash': 'dbm.bsd',
'dumbdbm': 'dbm.dumb',
'dbm': 'dbm.ndbm',
'gdbm': 'dbm.gnu',
'xmlrpclib': 'xmlrpc.client',
'DocXMLRPCServer': 'xmlrpc.server',
'SimpleXMLRPCServer': 'xmlrpc.server',
'httplib': 'http.client',
'htmlentitydefs' : 'html.entities',
'HTMLParser' : 'html.parser',
'Cookie': 'http.cookies',
'cookielib': 'http.cookiejar',
'BaseHTTPServer': 'http.server',
'SimpleHTTPServer': 'http.server',
'CGIHTTPServer': 'http.server',
'test.test_support': 'test.support',
'commands': 'subprocess',
'UserString' : 'collections',
'UserList' : 'collections',
'urlparse' : 'urllib.parse',
'robotparser' : 'urllib.robotparser',
'whichdb': 'dbm',
'anydbm': 'dbm'
}
# This contains rename rules that are easy to handle. We ignore the more
# complex stuff (e.g. mapping the names in the urllib and types modules).
# These rules should be run before import names are fixed.
NAME_MAPPING = {
('__builtin__', 'xrange'): ('builtins', 'range'),
('__builtin__', 'reduce'): ('functools', 'reduce'),
('__builtin__', 'intern'): ('sys', 'intern'),
('__builtin__', 'unichr'): ('builtins', 'chr'),
('__builtin__', 'basestring'): ('builtins', 'str'),
('__builtin__', 'long'): ('builtins', 'int'),
('itertools', 'izip'): ('builtins', 'zip'),
('itertools', 'imap'): ('builtins', 'map'),
('itertools', 'ifilter'): ('builtins', 'filter'),
('itertools', 'ifilterfalse'): ('itertools', 'filterfalse'),
}
# Same, but for 3.x to 2.x
REVERSE_IMPORT_MAPPING = dict((v, k) for (k, v) in IMPORT_MAPPING.items())
REVERSE_NAME_MAPPING = dict((v, k) for (k, v) in NAME_MAPPING.items())
...@@ -34,6 +34,7 @@ import struct ...@@ -34,6 +34,7 @@ import struct
import re import re
import io import io
import codecs import codecs
import _compat_pickle
__all__ = ["PickleError", "PicklingError", "UnpicklingError", "Pickler", __all__ = ["PickleError", "PicklingError", "UnpicklingError", "Pickler",
"Unpickler", "dump", "dumps", "load", "loads"] "Unpickler", "dump", "dumps", "load", "loads"]
...@@ -171,12 +172,11 @@ SHORT_BINBYTES = b'C' # " " ; " " " " < 256 bytes ...@@ -171,12 +172,11 @@ SHORT_BINBYTES = b'C' # " " ; " " " " < 256 bytes
__all__.extend([x for x in dir() if re.match("[A-Z][A-Z0-9_]+$",x)]) __all__.extend([x for x in dir() if re.match("[A-Z][A-Z0-9_]+$",x)])
# Pickling machinery # Pickling machinery
class _Pickler: class _Pickler:
def __init__(self, file, protocol=None): def __init__(self, file, protocol=None, *, fix_imports=True):
"""This takes a binary file for writing a pickle data stream. """This takes a binary file for writing a pickle data stream.
The optional protocol argument tells the pickler to use the The optional protocol argument tells the pickler to use the
...@@ -193,6 +193,10 @@ class _Pickler: ...@@ -193,6 +193,10 @@ class _Pickler:
bytes argument. It can thus be a file object opened for binary bytes argument. It can thus be a file object opened for binary
writing, a io.BytesIO instance, or any other custom object that writing, a io.BytesIO instance, or any other custom object that
meets this interface. meets this interface.
If fix_imports is True and protocol is less than 3, pickle will try to
map the new Python 3.x names to the old module names used in Python
2.x, so that the pickle data stream is readable with Python 2.x.
""" """
if protocol is None: if protocol is None:
protocol = DEFAULT_PROTOCOL protocol = DEFAULT_PROTOCOL
...@@ -208,6 +212,7 @@ class _Pickler: ...@@ -208,6 +212,7 @@ class _Pickler:
self.proto = int(protocol) self.proto = int(protocol)
self.bin = protocol >= 1 self.bin = protocol >= 1
self.fast = 0 self.fast = 0
self.fix_imports = fix_imports and protocol < 3
def clear_memo(self): def clear_memo(self):
"""Clears the pickler's "memo". """Clears the pickler's "memo".
...@@ -698,6 +703,11 @@ class _Pickler: ...@@ -698,6 +703,11 @@ class _Pickler:
write(GLOBAL + bytes(module, "utf-8") + b'\n' + write(GLOBAL + bytes(module, "utf-8") + b'\n' +
bytes(name, "utf-8") + b'\n') bytes(name, "utf-8") + b'\n')
else: else:
if self.fix_imports:
if (module, name) in _compat_pickle.REVERSE_NAME_MAPPING:
module, name = _compat_pickle.REVERSE_NAME_MAPPING[(module, name)]
if module in _compat_pickle.REVERSE_IMPORT_MAPPING:
module = _compat_pickle.REVERSE_IMPORT_MAPPING[module]
try: try:
write(GLOBAL + bytes(module, "ascii") + b'\n' + write(GLOBAL + bytes(module, "ascii") + b'\n' +
bytes(name, "ascii") + b'\n') bytes(name, "ascii") + b'\n')
...@@ -766,7 +776,8 @@ def whichmodule(func, funcname): ...@@ -766,7 +776,8 @@ def whichmodule(func, funcname):
class _Unpickler: class _Unpickler:
def __init__(self, file, *, encoding="ASCII", errors="strict"): def __init__(self, file, *, fix_imports=True,
encoding="ASCII", errors="strict"):
"""This takes a binary file for reading a pickle data stream. """This takes a binary file for reading a pickle data stream.
The protocol version of the pickle is detected automatically, so no The protocol version of the pickle is detected automatically, so no
...@@ -779,15 +790,21 @@ class _Unpickler: ...@@ -779,15 +790,21 @@ class _Unpickler:
reading, a BytesIO object, or any other custom object that reading, a BytesIO object, or any other custom object that
meets this interface. meets this interface.
Optional keyword arguments are encoding and errors, which are Optional keyword arguments are *fix_imports*, *encoding* and *errors*,
used to decode 8-bit string instances pickled by Python 2.x. which are used to control compatiblity support for pickle stream
These default to 'ASCII' and 'strict', respectively. generated by Python 2.x. If *fix_imports* is True, pickle will try to
map the old Python 2.x names to the new names used in Python 3.x. The
*encoding* and *errors* tell pickle how to decode 8-bit string
instances pickled by Python 2.x; these default to 'ASCII' and
'strict', respectively.
""" """
self.readline = file.readline self.readline = file.readline
self.read = file.read self.read = file.read
self.memo = {} self.memo = {}
self.encoding = encoding self.encoding = encoding
self.errors = errors self.errors = errors
self.proto = 0
self.fix_imports = fix_imports
def load(self): def load(self):
"""Read a pickled object representation from the open file. """Read a pickled object representation from the open file.
...@@ -838,6 +855,7 @@ class _Unpickler: ...@@ -838,6 +855,7 @@ class _Unpickler:
proto = ord(self.read(1)) proto = ord(self.read(1))
if not 0 <= proto <= HIGHEST_PROTOCOL: if not 0 <= proto <= HIGHEST_PROTOCOL:
raise ValueError("unsupported pickle protocol: %d" % proto) raise ValueError("unsupported pickle protocol: %d" % proto)
self.proto = proto
dispatch[PROTO[0]] = load_proto dispatch[PROTO[0]] = load_proto
def load_persid(self): def load_persid(self):
...@@ -1088,7 +1106,12 @@ class _Unpickler: ...@@ -1088,7 +1106,12 @@ class _Unpickler:
self.append(obj) self.append(obj)
def find_class(self, module, name): def find_class(self, module, name):
# Subclasses may override this # Subclasses may override this.
if self.proto < 3 and self.fix_imports:
if (module, name) in _compat_pickle.NAME_MAPPING:
module, name = _compat_pickle.NAME_MAPPING[(module, name)]
if module in _compat_pickle.IMPORT_MAPPING:
module = _compat_pickle.IMPORT_MAPPING[module]
__import__(module, level=0) __import__(module, level=0)
mod = sys.modules[module] mod = sys.modules[module]
klass = getattr(mod, name) klass = getattr(mod, name)
...@@ -1327,27 +1350,28 @@ except ImportError: ...@@ -1327,27 +1350,28 @@ except ImportError:
# Shorthands # Shorthands
def dump(obj, file, protocol=None): def dump(obj, file, protocol=None, *, fix_imports=True):
Pickler(file, protocol).dump(obj) Pickler(file, protocol, fix_imports=fix_imports).dump(obj)
def dumps(obj, protocol=None): def dumps(obj, protocol=None, *, fix_imports=True):
f = io.BytesIO() f = io.BytesIO()
Pickler(f, protocol).dump(obj) Pickler(f, protocol, fix_imports=fix_imports).dump(obj)
res = f.getvalue() res = f.getvalue()
assert isinstance(res, bytes_types) assert isinstance(res, bytes_types)
return res return res
def load(file, *, encoding="ASCII", errors="strict"): def load(file, *, fix_imports=True, encoding="ASCII", errors="strict"):
return Unpickler(file, encoding=encoding, errors=errors).load() return Unpickler(file, fix_imports=fix_imports,
encoding=encoding, errors=errors).load()
def loads(s, *, encoding="ASCII", errors="strict"): def loads(s, *, fix_imports=True, encoding="ASCII", errors="strict"):
if isinstance(s, str): if isinstance(s, str):
raise TypeError("Can't load pickle from unicode string") raise TypeError("Can't load pickle from unicode string")
file = io.BytesIO(s) file = io.BytesIO(s)
return Unpickler(file, encoding=encoding, errors=errors).load() return Unpickler(file, fix_imports=fix_imports,
encoding=encoding, errors=errors).load()
# Doctest # Doctest
def _test(): def _test():
import doctest import doctest
return doctest.testmod() return doctest.testmod()
......
...@@ -2066,27 +2066,27 @@ _dis_test = r""" ...@@ -2066,27 +2066,27 @@ _dis_test = r"""
29: ( MARK 29: ( MARK
30: d DICT (MARK at 29) 30: d DICT (MARK at 29)
31: p PUT 2 31: p PUT 2
34: c GLOBAL 'builtins bytes' 34: c GLOBAL '__builtin__ bytes'
50: p PUT 3 53: p PUT 3
53: ( MARK 56: ( MARK
54: ( MARK 57: ( MARK
55: l LIST (MARK at 54) 58: l LIST (MARK at 57)
56: p PUT 4 59: p PUT 4
59: L LONG 97 62: L LONG 97
64: a APPEND 67: a APPEND
65: L LONG 98 68: L LONG 98
70: a APPEND 73: a APPEND
71: L LONG 99 74: L LONG 99
76: a APPEND 79: a APPEND
77: t TUPLE (MARK at 53) 80: t TUPLE (MARK at 56)
78: p PUT 5 81: p PUT 5
81: R REDUCE 84: R REDUCE
82: p PUT 6 85: p PUT 6
85: V UNICODE 'def' 88: V UNICODE 'def'
90: p PUT 7 93: p PUT 7
93: s SETITEM 96: s SETITEM
94: a APPEND 97: a APPEND
95: . STOP 98: . STOP
highest protocol among opcodes = 0 highest protocol among opcodes = 0
Try again with a "binary" pickle. Try again with a "binary" pickle.
...@@ -2105,25 +2105,25 @@ Try again with a "binary" pickle. ...@@ -2105,25 +2105,25 @@ Try again with a "binary" pickle.
14: q BINPUT 1 14: q BINPUT 1
16: } EMPTY_DICT 16: } EMPTY_DICT
17: q BINPUT 2 17: q BINPUT 2
19: c GLOBAL 'builtins bytes' 19: c GLOBAL '__builtin__ bytes'
35: q BINPUT 3 38: q BINPUT 3
37: ( MARK 40: ( MARK
38: ] EMPTY_LIST 41: ] EMPTY_LIST
39: q BINPUT 4 42: q BINPUT 4
41: ( MARK 44: ( MARK
42: K BININT1 97 45: K BININT1 97
44: K BININT1 98 47: K BININT1 98
46: K BININT1 99 49: K BININT1 99
48: e APPENDS (MARK at 41) 51: e APPENDS (MARK at 44)
49: t TUPLE (MARK at 37) 52: t TUPLE (MARK at 40)
50: q BINPUT 5 53: q BINPUT 5
52: R REDUCE 55: R REDUCE
53: q BINPUT 6 56: q BINPUT 6
55: X BINUNICODE 'def' 58: X BINUNICODE 'def'
63: q BINPUT 7 66: q BINPUT 7
65: s SETITEM 68: s SETITEM
66: e APPENDS (MARK at 3) 69: e APPENDS (MARK at 3)
67: . STOP 70: . STOP
highest protocol among opcodes = 1 highest protocol among opcodes = 1
Exercise the INST/OBJ/BUILD family. Exercise the INST/OBJ/BUILD family.
...@@ -2141,58 +2141,58 @@ highest protocol among opcodes = 0 ...@@ -2141,58 +2141,58 @@ highest protocol among opcodes = 0
0: ( MARK 0: ( MARK
1: l LIST (MARK at 0) 1: l LIST (MARK at 0)
2: p PUT 0 2: p PUT 0
5: c GLOBAL 'copyreg _reconstructor' 5: c GLOBAL 'copy_reg _reconstructor'
29: p PUT 1 30: p PUT 1
32: ( MARK 33: ( MARK
33: c GLOBAL 'pickletools _Example' 34: c GLOBAL 'pickletools _Example'
55: p PUT 2 56: p PUT 2
58: c GLOBAL 'builtins object' 59: c GLOBAL '__builtin__ object'
75: p PUT 3 79: p PUT 3
78: N NONE 82: N NONE
79: t TUPLE (MARK at 32) 83: t TUPLE (MARK at 33)
80: p PUT 4 84: p PUT 4
83: R REDUCE 87: R REDUCE
84: p PUT 5 88: p PUT 5
87: ( MARK 91: ( MARK
88: d DICT (MARK at 87) 92: d DICT (MARK at 91)
89: p PUT 6 93: p PUT 6
92: V UNICODE 'value' 96: V UNICODE 'value'
99: p PUT 7 103: p PUT 7
102: L LONG 42 106: L LONG 42
107: s SETITEM 111: s SETITEM
108: b BUILD 112: b BUILD
109: a APPEND
110: g GET 5
113: a APPEND 113: a APPEND
114: . STOP 114: g GET 5
117: a APPEND
118: . STOP
highest protocol among opcodes = 0 highest protocol among opcodes = 0
>>> dis(pickle.dumps(x, 1)) >>> dis(pickle.dumps(x, 1))
0: ] EMPTY_LIST 0: ] EMPTY_LIST
1: q BINPUT 0 1: q BINPUT 0
3: ( MARK 3: ( MARK
4: c GLOBAL 'copyreg _reconstructor' 4: c GLOBAL 'copy_reg _reconstructor'
28: q BINPUT 1 29: q BINPUT 1
30: ( MARK 31: ( MARK
31: c GLOBAL 'pickletools _Example' 32: c GLOBAL 'pickletools _Example'
53: q BINPUT 2 54: q BINPUT 2
55: c GLOBAL 'builtins object' 56: c GLOBAL '__builtin__ object'
72: q BINPUT 3 76: q BINPUT 3
74: N NONE 78: N NONE
75: t TUPLE (MARK at 30) 79: t TUPLE (MARK at 31)
76: q BINPUT 4 80: q BINPUT 4
78: R REDUCE 82: R REDUCE
79: q BINPUT 5 83: q BINPUT 5
81: } EMPTY_DICT 85: } EMPTY_DICT
82: q BINPUT 6 86: q BINPUT 6
84: X BINUNICODE 'value' 88: X BINUNICODE 'value'
94: q BINPUT 7 98: q BINPUT 7
96: K BININT1 42 100: K BININT1 42
98: s SETITEM 102: s SETITEM
99: b BUILD 103: b BUILD
100: h BINGET 5 104: h BINGET 5
102: e APPENDS (MARK at 3) 106: e APPENDS (MARK at 3)
103: . STOP 107: . STOP
highest protocol among opcodes = 1 highest protocol among opcodes = 1
Try "the canonical" recursive-object test. Try "the canonical" recursive-object test.
......
...@@ -3,6 +3,7 @@ import unittest ...@@ -3,6 +3,7 @@ import unittest
import pickle import pickle
import pickletools import pickletools
import copyreg import copyreg
from http.cookies import SimpleCookie
from test.support import TestFailed, TESTFN, run_with_locale from test.support import TestFailed, TESTFN, run_with_locale
...@@ -342,6 +343,24 @@ DATA2_DIS = """\ ...@@ -342,6 +343,24 @@ DATA2_DIS = """\
highest protocol among opcodes = 2 highest protocol among opcodes = 2
""" """
# set([1,2]) pickled from 2.x with protocol 2
DATA3 = b'\x80\x02c__builtin__\nset\nq\x00]q\x01(K\x01K\x02e\x85q\x02Rq\x03.'
# xrange(5) pickled from 2.x with protocol 2
DATA4 = b'\x80\x02c__builtin__\nxrange\nq\x00K\x00K\x05K\x01\x87q\x01Rq\x02.'
# a SimpleCookie() object pickled from 2.x with protocol 2
DATA5 = (b'\x80\x02cCookie\nSimpleCookie\nq\x00)\x81q\x01U\x03key'
b'q\x02cCookie\nMorsel\nq\x03)\x81q\x04(U\x07commentq\x05U'
b'\x00q\x06U\x06domainq\x07h\x06U\x06secureq\x08h\x06U\x07'
b'expiresq\th\x06U\x07max-ageq\nh\x06U\x07versionq\x0bh\x06U'
b'\x04pathq\x0ch\x06U\x08httponlyq\rh\x06u}q\x0e(U\x0b'
b'coded_valueq\x0fU\x05valueq\x10h\x10h\x10h\x02h\x02ubs}q\x11b.')
# set([3]) pickled from 2.x with protocol 2
DATA6 = b'\x80\x02c__builtin__\nset\nq\x00]q\x01K\x03a\x85q\x02Rq\x03.'
def create_data(): def create_data():
c = C() c = C()
c.foo = 1 c.foo = 1
...@@ -956,6 +975,29 @@ class AbstractPickleTests(unittest.TestCase): ...@@ -956,6 +975,29 @@ class AbstractPickleTests(unittest.TestCase):
for x_key, y_key in zip(x_keys, y_keys): for x_key, y_key in zip(x_keys, y_keys):
self.assertIs(x_key, y_key) self.assertIs(x_key, y_key)
def test_unpickle_from_2x(self):
# Unpickle non-trivial data from Python 2.x.
loaded = self.loads(DATA3)
self.assertEqual(loaded, set([1, 2]))
loaded = self.loads(DATA4)
self.assertEqual(type(loaded), type(range(0)))
self.assertEqual(list(loaded), list(range(5)))
loaded = self.loads(DATA5)
self.assertEqual(type(loaded), SimpleCookie)
self.assertEqual(list(loaded.keys()), ["key"])
self.assertEqual(loaded["key"].value, "Set-Cookie: key=value")
def test_pickle_to_2x(self):
# Pickle non-trivial data with protocol 2, expecting that it yields
# the same result as Python 2.x did.
# NOTE: this test is a bit too strong since we can produce different
# bytecode that 2.x will still understand.
dumped = self.dumps(range(5), 2)
self.assertEqual(dumped, DATA4)
dumped = self.dumps(set([3]), 2)
self.assertEqual(dumped, DATA6)
# Test classes for reduce_ex # Test classes for reduce_ex
class REX_one(object): class REX_one(object):
......
...@@ -12,6 +12,9 @@ class OptimizedPickleTests(AbstractPickleTests, AbstractPickleModuleTests): ...@@ -12,6 +12,9 @@ class OptimizedPickleTests(AbstractPickleTests, AbstractPickleModuleTests):
def loads(self, buf): def loads(self, buf):
return pickle.loads(buf) return pickle.loads(buf)
# Test relies on precise output of dumps()
test_pickle_to_2x = None
def test_main(): def test_main():
support.run_unittest(OptimizedPickleTests) support.run_unittest(OptimizedPickleTests)
......
...@@ -21,6 +21,11 @@ Core and Builtins ...@@ -21,6 +21,11 @@ Core and Builtins
Library Library
------- -------
- Issue #6137: The pickle module now translates module names when loading
or dumping pickles with a 2.x-compatible protocol, in order to make data
sharing and migration easier. This behaviour can be disabled using the
new `fix_imports` optional argument.
- Removed the ipaddr module. - Removed the ipaddr module.
- Issue #3613: base64.{encode,decode}string are now called - Issue #3613: base64.{encode,decode}string are now called
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment