diff options
-rw-r--r-- | Lib/pickle.py | 10 | ||||
-rw-r--r-- | Lib/test/pickletester.py | 58 | ||||
-rw-r--r-- | Misc/NEWS | 3 |
3 files changed, 65 insertions, 6 deletions
diff --git a/Lib/pickle.py b/Lib/pickle.py index d121ec9..b3b775f 100644 --- a/Lib/pickle.py +++ b/Lib/pickle.py @@ -1156,8 +1156,14 @@ class _Unpickler: def load_appends(self): stack = self.stack mark = self.marker() - list = stack[mark - 1] - list.extend(stack[mark + 1:]) + list_obj = stack[mark - 1] + items = stack[mark + 1:] + if isinstance(list_obj, list): + list_obj.extend(items) + else: + append = list_obj.append + for item in items: + append(item) del stack[mark:] dispatch[APPENDS[0]] = load_appends diff --git a/Lib/test/pickletester.py b/Lib/test/pickletester.py index c58b876..7e6e758 100644 --- a/Lib/test/pickletester.py +++ b/Lib/test/pickletester.py @@ -1219,6 +1219,29 @@ class AbstractPickleTests(unittest.TestCase): for p, expected in goodpickles: self.assertEqual(self.loads(p), expected) + def _check_pickling_with_opcode(self, obj, opcode, proto): + pickled = self.dumps(obj, proto) + self.assertTrue(opcode_in_pickle(opcode, pickled)) + unpickled = self.loads(pickled) + self.assertEqual(obj, unpickled) + + def test_appends_on_non_lists(self): + # Issue #17720 + obj = REX_six([1, 2, 3]) + for proto in protocols: + if proto == 0: + self._check_pickling_with_opcode(obj, pickle.APPEND, proto) + else: + self._check_pickling_with_opcode(obj, pickle.APPENDS, proto) + + def test_setitems_on_non_dicts(self): + obj = REX_seven({1: -1, 2: -2, 3: -3}) + for proto in protocols: + if proto == 0: + self._check_pickling_with_opcode(obj, pickle.SETITEM, proto) + else: + self._check_pickling_with_opcode(obj, pickle.SETITEMS, proto) + class BigmemPickleTests(unittest.TestCase): @@ -1304,18 +1327,18 @@ class BigmemPickleTests(unittest.TestCase): # Test classes for reduce_ex class REX_one(object): + """No __reduce_ex__ here, but inheriting it from object""" _reduce_called = 0 def __reduce__(self): self._reduce_called = 1 return REX_one, () - # No __reduce_ex__ here, but inheriting it from object class REX_two(object): + """No __reduce__ here, but inheriting it from object""" _proto = None def __reduce_ex__(self, proto): self._proto = proto return REX_two, () - # No __reduce__ here, but inheriting it from object class REX_three(object): _proto = None @@ -1326,18 +1349,45 @@ class REX_three(object): raise TestFailed("This __reduce__ shouldn't be called") class REX_four(object): + """Calling base class method should succeed""" _proto = None def __reduce_ex__(self, proto): self._proto = proto return object.__reduce_ex__(self, proto) - # Calling base class method should succeed class REX_five(object): + """This one used to fail with infinite recursion""" _reduce_called = 0 def __reduce__(self): self._reduce_called = 1 return object.__reduce__(self) - # This one used to fail with infinite recursion + +class REX_six(object): + """This class is used to check the 4th argument (list iterator) of the reduce + protocol. + """ + def __init__(self, items=None): + self.items = items if items is not None else [] + def __eq__(self, other): + return type(self) is type(other) and self.items == self.items + def append(self, item): + self.items.append(item) + def __reduce__(self): + return type(self), (), None, iter(self.items), None + +class REX_seven(object): + """This class is used to check the 5th argument (dict iterator) of the reduce + protocol. + """ + def __init__(self, table=None): + self.table = table if table is not None else {} + def __eq__(self, other): + return type(self) is type(other) and self.table == self.table + def __setitem__(self, key, value): + self.table[key] = value + def __reduce__(self): + return type(self), (), None, None, iter(self.table.items()) + # Test classes for newobj @@ -64,6 +64,9 @@ Library - Issue #17707: multiprocessing.Queue's get() method does not block for short timeouts. +- Isuse #17720: Fix the Python implementation of pickle.Unpickler to correctly + process the APPENDS opcode when it is used on non-list objects. + - Issue #17012: shutil.which() no longer fallbacks to the PATH environment variable if empty path argument is specified. Patch by Serhiy Storchaka. |