diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index 112495512e192c..4f06fd9db0322f 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -1349,6 +1349,28 @@ def test_readonly_attributes(self): with self.assertRaises(AttributeError): buf.raw = x + def test_pickling_subclass(self): + global MyBufferedIO + class MyBufferedIO(self.tp): + def __init__(self, raw, tag): + super().__init__(raw) + self.tag = tag + def __getstate__(self): + return self.tag, self.raw.getvalue() + def __setstate__(slf, state): + tag, value = state + slf.__init__(self.BytesIO(value), tag) + + raw = self.BytesIO(b'data') + buf = MyBufferedIO(raw, tag='ham') + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(protocol=proto): + pickled = pickle.dumps(buf, proto) + newbuf = pickle.loads(pickled) + self.assertEqual(newbuf.raw.getvalue(), b'data') + self.assertEqual(newbuf.tag, 'ham') + del MyBufferedIO + class SizeofTest: @@ -3932,6 +3954,28 @@ def test_issue35928(self): f.write(res) self.assertEqual(res + f.readline(), 'foo\nbar\n') + def test_pickling_subclass(self): + global MyTextIO + class MyTextIO(self.TextIOWrapper): + def __init__(self, raw, tag): + super().__init__(raw) + self.tag = tag + def __getstate__(self): + return self.tag, self.buffer.getvalue() + def __setstate__(slf, state): + tag, value = state + slf.__init__(self.BytesIO(value), tag) + + raw = self.BytesIO(b'data') + txt = MyTextIO(raw, 'ham') + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(protocol=proto): + pickled = pickle.dumps(txt, proto) + newtxt = pickle.loads(pickled) + self.assertEqual(newtxt.buffer.getvalue(), b'data') + self.assertEqual(newtxt.tag, 'ham') + del MyTextIO + class MemviewBytesIO(io.BytesIO): '''A BytesIO object whose read method returns memoryviews diff --git a/Misc/NEWS.d/next/Library/2024-08-02-20-01-36.gh-issue-122559.2JlJr3.rst b/Misc/NEWS.d/next/Library/2024-08-02-20-01-36.gh-issue-122559.2JlJr3.rst new file mode 100644 index 00000000000000..4ef9daa1bac26f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-08-02-20-01-36.gh-issue-122559.2JlJr3.rst @@ -0,0 +1,6 @@ +Remove :meth:`!__reduce__` and :meth:`!__reduce_ex__` methods that always +raise :exc:`TypeError` in the C implementation of :class:`io.FileIO`, +:class:`io.BufferedReader`, :class:`io.BufferedWriter` and +:class:`io.BufferedRandom` and replace them with default +:meth:`!__getstate__` methods that raise :exc:`!TypeError`. +This restores fine details of behavior of Python 3.11 and older versions. diff --git a/Modules/_io/bufferedio.c b/Modules/_io/bufferedio.c index e45323c93a17ef..050878c6da6ec3 100644 --- a/Modules/_io/bufferedio.c +++ b/Modules/_io/bufferedio.c @@ -2530,8 +2530,7 @@ static PyMethodDef bufferedreader_methods[] = { _IO__BUFFERED_TRUNCATE_METHODDEF _IO__BUFFERED___SIZEOF___METHODDEF - {"__reduce__", _PyIOBase_cannot_pickle, METH_NOARGS}, - {"__reduce_ex__", _PyIOBase_cannot_pickle, METH_O}, + {"__getstate__", _PyIOBase_cannot_pickle, METH_NOARGS}, {NULL, NULL} }; @@ -2590,8 +2589,7 @@ static PyMethodDef bufferedwriter_methods[] = { _IO__BUFFERED_TELL_METHODDEF _IO__BUFFERED___SIZEOF___METHODDEF - {"__reduce__", _PyIOBase_cannot_pickle, METH_NOARGS}, - {"__reduce_ex__", _PyIOBase_cannot_pickle, METH_O}, + {"__getstate__", _PyIOBase_cannot_pickle, METH_NOARGS}, {NULL, NULL} }; @@ -2708,8 +2706,7 @@ static PyMethodDef bufferedrandom_methods[] = { _IO_BUFFEREDWRITER_WRITE_METHODDEF _IO__BUFFERED___SIZEOF___METHODDEF - {"__reduce__", _PyIOBase_cannot_pickle, METH_NOARGS}, - {"__reduce_ex__", _PyIOBase_cannot_pickle, METH_O}, + {"__getstate__", _PyIOBase_cannot_pickle, METH_NOARGS}, {NULL, NULL} }; diff --git a/Modules/_io/fileio.c b/Modules/_io/fileio.c index b5129ffcbffdcf..545eb4acf9b10e 100644 --- a/Modules/_io/fileio.c +++ b/Modules/_io/fileio.c @@ -1178,8 +1178,7 @@ static PyMethodDef fileio_methods[] = { _IO_FILEIO_FILENO_METHODDEF _IO_FILEIO_ISATTY_METHODDEF {"_dealloc_warn", (PyCFunction)fileio_dealloc_warn, METH_O, NULL}, - {"__reduce__", _PyIOBase_cannot_pickle, METH_NOARGS}, - {"__reduce_ex__", _PyIOBase_cannot_pickle, METH_O}, + {"__getstate__", _PyIOBase_cannot_pickle, METH_NOARGS}, {NULL, NULL} /* sentinel */ }; diff --git a/Modules/_io/textio.c b/Modules/_io/textio.c index c162d8106ec1fd..e10689d05a8400 100644 --- a/Modules/_io/textio.c +++ b/Modules/_io/textio.c @@ -3350,8 +3350,7 @@ static PyMethodDef textiowrapper_methods[] = { _IO_TEXTIOWRAPPER_TELL_METHODDEF _IO_TEXTIOWRAPPER_TRUNCATE_METHODDEF - {"__reduce__", _PyIOBase_cannot_pickle, METH_NOARGS}, - {"__reduce_ex__", _PyIOBase_cannot_pickle, METH_O}, + {"__getstate__", _PyIOBase_cannot_pickle, METH_NOARGS}, {NULL, NULL} };