Skip to content

Commit e9253eb

Browse files
gh-122559: Synchronize C and Python implementation of the io module about pickling (GH-122628)
In the C implementation, remove __reduce__ and __reduce_ex__ methods that always raise TypeError and restore __getstate__ methods that always raise TypeErrori. This restores fine details of the pre-3.12 behavior and unifies both implementations.
1 parent a247dd3 commit e9253eb

File tree

5 files changed

+55
-10
lines changed

5 files changed

+55
-10
lines changed

Lib/test/test_io.py

+44
Original file line numberDiff line numberDiff line change
@@ -1373,6 +1373,28 @@ def test_readonly_attributes(self):
13731373
with self.assertRaises(AttributeError):
13741374
buf.raw = x
13751375

1376+
def test_pickling_subclass(self):
1377+
global MyBufferedIO
1378+
class MyBufferedIO(self.tp):
1379+
def __init__(self, raw, tag):
1380+
super().__init__(raw)
1381+
self.tag = tag
1382+
def __getstate__(self):
1383+
return self.tag, self.raw.getvalue()
1384+
def __setstate__(slf, state):
1385+
tag, value = state
1386+
slf.__init__(self.BytesIO(value), tag)
1387+
1388+
raw = self.BytesIO(b'data')
1389+
buf = MyBufferedIO(raw, tag='ham')
1390+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1391+
with self.subTest(protocol=proto):
1392+
pickled = pickle.dumps(buf, proto)
1393+
newbuf = pickle.loads(pickled)
1394+
self.assertEqual(newbuf.raw.getvalue(), b'data')
1395+
self.assertEqual(newbuf.tag, 'ham')
1396+
del MyBufferedIO
1397+
13761398

13771399
class SizeofTest:
13781400

@@ -3950,6 +3972,28 @@ def test_issue35928(self):
39503972
f.write(res)
39513973
self.assertEqual(res + f.readline(), 'foo\nbar\n')
39523974

3975+
def test_pickling_subclass(self):
3976+
global MyTextIO
3977+
class MyTextIO(self.TextIOWrapper):
3978+
def __init__(self, raw, tag):
3979+
super().__init__(raw)
3980+
self.tag = tag
3981+
def __getstate__(self):
3982+
return self.tag, self.buffer.getvalue()
3983+
def __setstate__(slf, state):
3984+
tag, value = state
3985+
slf.__init__(self.BytesIO(value), tag)
3986+
3987+
raw = self.BytesIO(b'data')
3988+
txt = MyTextIO(raw, 'ham')
3989+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
3990+
with self.subTest(protocol=proto):
3991+
pickled = pickle.dumps(txt, proto)
3992+
newtxt = pickle.loads(pickled)
3993+
self.assertEqual(newtxt.buffer.getvalue(), b'data')
3994+
self.assertEqual(newtxt.tag, 'ham')
3995+
del MyTextIO
3996+
39533997
@unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()")
39543998
def test_read_non_blocking(self):
39553999
import os
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Remove :meth:`!__reduce__` and :meth:`!__reduce_ex__` methods that always
2+
raise :exc:`TypeError` in the C implementation of :class:`io.FileIO`,
3+
:class:`io.BufferedReader`, :class:`io.BufferedWriter` and
4+
:class:`io.BufferedRandom` and replace them with default
5+
:meth:`!__getstate__` methods that raise :exc:`!TypeError`.
6+
This restores fine details of behavior of Python 3.11 and older versions.

Modules/_io/bufferedio.c

+3-6
Original file line numberDiff line numberDiff line change
@@ -2555,8 +2555,7 @@ static PyMethodDef bufferedreader_methods[] = {
25552555
_IO__BUFFERED_TRUNCATE_METHODDEF
25562556
_IO__BUFFERED___SIZEOF___METHODDEF
25572557

2558-
{"__reduce__", _PyIOBase_cannot_pickle, METH_NOARGS},
2559-
{"__reduce_ex__", _PyIOBase_cannot_pickle, METH_O},
2558+
{"__getstate__", _PyIOBase_cannot_pickle, METH_NOARGS},
25602559
{NULL, NULL}
25612560
};
25622561

@@ -2615,8 +2614,7 @@ static PyMethodDef bufferedwriter_methods[] = {
26152614
_IO__BUFFERED_TELL_METHODDEF
26162615
_IO__BUFFERED___SIZEOF___METHODDEF
26172616

2618-
{"__reduce__", _PyIOBase_cannot_pickle, METH_NOARGS},
2619-
{"__reduce_ex__", _PyIOBase_cannot_pickle, METH_O},
2617+
{"__getstate__", _PyIOBase_cannot_pickle, METH_NOARGS},
26202618
{NULL, NULL}
26212619
};
26222620

@@ -2733,8 +2731,7 @@ static PyMethodDef bufferedrandom_methods[] = {
27332731
_IO_BUFFEREDWRITER_WRITE_METHODDEF
27342732
_IO__BUFFERED___SIZEOF___METHODDEF
27352733

2736-
{"__reduce__", _PyIOBase_cannot_pickle, METH_NOARGS},
2737-
{"__reduce_ex__", _PyIOBase_cannot_pickle, METH_O},
2734+
{"__getstate__", _PyIOBase_cannot_pickle, METH_NOARGS},
27382735
{NULL, NULL}
27392736
};
27402737

Modules/_io/fileio.c

+1-2
Original file line numberDiff line numberDiff line change
@@ -1262,8 +1262,7 @@ static PyMethodDef fileio_methods[] = {
12621262
_IO_FILEIO_ISATTY_METHODDEF
12631263
{"_isatty_open_only", _io_FileIO_isatty_open_only, METH_NOARGS},
12641264
{"_dealloc_warn", fileio_dealloc_warn, METH_O, NULL},
1265-
{"__reduce__", _PyIOBase_cannot_pickle, METH_NOARGS},
1266-
{"__reduce_ex__", _PyIOBase_cannot_pickle, METH_O},
1265+
{"__getstate__", _PyIOBase_cannot_pickle, METH_NOARGS},
12671266
{NULL, NULL} /* sentinel */
12681267
};
12691268

Modules/_io/textio.c

+1-2
Original file line numberDiff line numberDiff line change
@@ -3366,8 +3366,7 @@ static PyMethodDef textiowrapper_methods[] = {
33663366
_IO_TEXTIOWRAPPER_TELL_METHODDEF
33673367
_IO_TEXTIOWRAPPER_TRUNCATE_METHODDEF
33683368

3369-
{"__reduce__", _PyIOBase_cannot_pickle, METH_NOARGS},
3370-
{"__reduce_ex__", _PyIOBase_cannot_pickle, METH_O},
3369+
{"__getstate__", _PyIOBase_cannot_pickle, METH_NOARGS},
33713370
{NULL, NULL}
33723371
};
33733372

0 commit comments

Comments
 (0)