Index: Lib/test/pickletester.py =================================================================== --- Lib/test/pickletester.py (revision 82333) +++ Lib/test/pickletester.py (working copy) @@ -1295,7 +1295,36 @@ f.seek(0) self.assertEqual(unpickler.load(), data2) + def test_pickling_circular_refs(self): + # See issue # 1581183. + for proto in protocols: + f = io.BytesIO() + pickler = self.pickler_class(f, protocol=proto) + x = Y().x + assert x.a.x is x + pickler.dump(x) + pickled = f.getvalue() + f = io.BytesIO() + f.write(pickled) + f.seek(0) + unpickler = self.unpickler_class(f) + x = unpickler.load() + self.assertIs(x.a.x, x, "failed for proto=%d" % proto) + +class X: + def __new__(cls, a): + res = object.__new__(cls) + res.a = a + return res + def __getnewargs__(self): + return (self.a,) + +class Y: + def __init__(self): + self.x = X(self) + + if __name__ == "__main__": # Print some stuff that can be used to rewrite DATA{0,1,2} from pickletools import dis Index: Lib/pickle.py =================================================================== --- Lib/pickle.py (revision 82333) +++ Lib/pickle.py (working copy) @@ -254,10 +254,10 @@ # growable) array, indexed by memo key. if self.fast: return - assert id(obj) not in self.memo - memo_len = len(self.memo) - self.write(self.put(memo_len)) - self.memo[id(obj)] = memo_len, obj + if id(obj) not in self.memo: + memo_len = len(self.memo) + self.write(self.put(memo_len)) + self.memo[id(obj)] = memo_len, obj # Return a PUT (BINPUT, LONG_BINPUT) opcode string, with argument i. def put(self, i, pack=struct.pack):