diff options
author | Serhiy Storchaka <storchaka@gmail.com> | 2024-07-25 08:45:19 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-07-25 08:45:19 (GMT) |
commit | dc07f65a53baf60d9857186294d3d7ba92d5606d (patch) | |
tree | ad5e883c9e083b1f6af015b1fb2829a04abfad96 /Lib | |
parent | ca0f7c447c83503bd760dc2eb6d1ea4b3558f8e9 (diff) | |
download | cpython-dc07f65a53baf60d9857186294d3d7ba92d5606d.zip cpython-dc07f65a53baf60d9857186294d3d7ba92d5606d.tar.gz cpython-dc07f65a53baf60d9857186294d3d7ba92d5606d.tar.bz2 |
gh-82951: Fix serializing by name in pickle protocols < 4 (GH-122149)
Serializing objects with complex __qualname__ (such as unbound methods and
nested classes) by name no longer involves serializing parent objects by value
in pickle protocols < 4.
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/pickle.py | 40 | ||||
-rw-r--r-- | Lib/test/pickletester.py | 12 |
2 files changed, 43 insertions, 9 deletions
diff --git a/Lib/pickle.py b/Lib/pickle.py index 115bd89..2d76498 100644 --- a/Lib/pickle.py +++ b/Lib/pickle.py @@ -1110,11 +1110,35 @@ class _Pickler: self.save(module_name) self.save(name) write(STACK_GLOBAL) - elif parent is not module: - self.save_reduce(getattr, (parent, lastname)) - elif self.proto >= 3: - write(GLOBAL + bytes(module_name, "utf-8") + b'\n' + - bytes(name, "utf-8") + b'\n') + elif '.' in name: + # In protocol < 4, objects with multi-part __qualname__ + # are represented as + # getattr(getattr(..., attrname1), attrname2). + dotted_path = name.split('.') + name = dotted_path.pop(0) + save = self.save + for attrname in dotted_path: + save(getattr) + if self.proto < 2: + write(MARK) + self._save_toplevel_by_name(module_name, name) + for attrname in dotted_path: + save(attrname) + if self.proto < 2: + write(TUPLE) + else: + write(TUPLE2) + write(REDUCE) + else: + self._save_toplevel_by_name(module_name, name) + + self.memoize(obj) + + def _save_toplevel_by_name(self, module_name, name): + if self.proto >= 3: + # Non-ASCII identifiers are supported only with protocols >= 3. + self.write(GLOBAL + bytes(module_name, "utf-8") + b'\n' + + bytes(name, "utf-8") + b'\n') else: if self.fix_imports: r_name_mapping = _compat_pickle.REVERSE_NAME_MAPPING @@ -1124,15 +1148,13 @@ class _Pickler: elif module_name in r_import_mapping: module_name = r_import_mapping[module_name] try: - write(GLOBAL + bytes(module_name, "ascii") + b'\n' + - bytes(name, "ascii") + b'\n') + self.write(GLOBAL + bytes(module_name, "ascii") + b'\n' + + bytes(name, "ascii") + b'\n') except UnicodeEncodeError: raise PicklingError( "can't pickle global identifier '%s.%s' using " "pickle protocol %i" % (module, name, self.proto)) from None - self.memoize(obj) - def save_type(self, obj): if obj is type(None): return self.save_reduce(type, (None,), obj=obj) diff --git a/Lib/test/pickletester.py b/Lib/test/pickletester.py index 9922591..1366322 100644 --- a/Lib/test/pickletester.py +++ b/Lib/test/pickletester.py @@ -2818,6 +2818,18 @@ class AbstractPickleTests: self.assertIs(unpickled, Recursive) del Recursive.mod # break reference loop + def test_recursive_nested_names2(self): + global Recursive + class Recursive: + pass + Recursive.ref = Recursive + Recursive.__qualname__ = 'Recursive.ref' + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(proto=proto): + unpickled = self.loads(self.dumps(Recursive, proto)) + self.assertIs(unpickled, Recursive) + del Recursive.ref # break reference loop + def test_py_methods(self): global PyMethodsTest class PyMethodsTest: |