diff --git a/Lib/pickle.py b/Lib/pickle.py index a67ac7dd8b6..71aa57d500e 100644 --- a/Lib/pickle.py +++ b/Lib/pickle.py @@ -36,10 +36,16 @@ import io import codecs import _compat_pickle -from _pickle import PickleBuffer - __all__ = ["PickleError", "PicklingError", "UnpicklingError", "Pickler", - "Unpickler", "dump", "dumps", "load", "loads", "PickleBuffer"] + "Unpickler", "dump", "dumps", "load", "loads"] + +try: + from _pickle import PickleBuffer + __all__.append("PickleBuffer") + _HAVE_PICKLE_BUFFER = True +except ImportError: + _HAVE_PICKLE_BUFFER = False + # Shortcut for use in isinstance testing bytes_types = (bytes, bytearray) @@ -812,31 +818,32 @@ class _Pickler: self.write(BYTEARRAY8 + pack("= 5") - with obj.raw() as m: - if not m.contiguous: - raise PicklingError("PickleBuffer can not be pickled when " - "pointing to a non-contiguous buffer") - in_band = True - if self._buffer_callback is not None: - in_band = bool(self._buffer_callback(obj)) - if in_band: - # Write data in-band - # XXX The C implementation avoids a copy here - if m.readonly: - self.save_bytes(m.tobytes()) + if _HAVE_PICKLE_BUFFER: + def save_picklebuffer(self, obj): + if self.proto < 5: + raise PicklingError("PickleBuffer can only pickled with " + "protocol >= 5") + with obj.raw() as m: + if not m.contiguous: + raise PicklingError("PickleBuffer can not be pickled when " + "pointing to a non-contiguous buffer") + in_band = True + if self._buffer_callback is not None: + in_band = bool(self._buffer_callback(obj)) + if in_band: + # Write data in-band + # XXX The C implementation avoids a copy here + if m.readonly: + self.save_bytes(m.tobytes()) + else: + self.save_bytearray(m.tobytes()) else: - self.save_bytearray(m.tobytes()) - else: - # Write data out-of-band - self.write(NEXT_BUFFER) - if m.readonly: - self.write(READONLY_BUFFER) + # Write data out-of-band + self.write(NEXT_BUFFER) + if m.readonly: + self.write(READONLY_BUFFER) - dispatch[PickleBuffer] = save_picklebuffer + dispatch[PickleBuffer] = save_picklebuffer def save_str(self, obj): if self.bin: diff --git a/Lib/test/test_pickle.py b/Lib/test/test_pickle.py index 5f7a879b935..2307b133dbd 100644 --- a/Lib/test/test_pickle.py +++ b/Lib/test/test_pickle.py @@ -203,6 +203,13 @@ class PyChainDispatchTableTests(AbstractDispatchTableTests): return collections.ChainMap({}, pickle.dispatch_table) +class PyPicklerHookTests(AbstractHookTests): + class CustomPyPicklerClass(pickle._Pickler, + AbstractCustomPicklerClass): + pass + pickler_class = CustomPyPicklerClass + + if has_c_implementation: class CPickleTests(AbstractPickleModuleTests): from _pickle import dump, dumps, load, loads, Pickler, Unpickler @@ -255,12 +262,6 @@ if has_c_implementation: def get_dispatch_table(self): return collections.ChainMap({}, pickle.dispatch_table) - class PyPicklerHookTests(AbstractHookTests): - class CustomPyPicklerClass(pickle._Pickler, - AbstractCustomPicklerClass): - pass - pickler_class = CustomPyPicklerClass - class CPicklerHookTests(AbstractHookTests): class CustomCPicklerClass(_pickle.Pickler, AbstractCustomPicklerClass): pass diff --git a/Misc/NEWS.d/next/Library/2019-06-12-16-10-50.bpo-37210.r4yMg6.rst b/Misc/NEWS.d/next/Library/2019-06-12-16-10-50.bpo-37210.r4yMg6.rst new file mode 100644 index 00000000000..58fc66b5905 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-06-12-16-10-50.bpo-37210.r4yMg6.rst @@ -0,0 +1 @@ +Allow pure Python implementation of :mod:`pickle` to work even when the C :mod:`_pickle` module is unavailable.