Skip to content

Commit 973e2d3

Browse files
[3.13] gh-122559: Synchronize C and Python implementation of the io module about pickling (GH-122628) (GH-133381)
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. (cherry picked from commit e9253eb)
1 parent 3c9d177 commit 973e2d3

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
@@ -1349,6 +1349,28 @@ def test_readonly_attributes(self):
13491349
with self.assertRaises(AttributeError):
13501350
buf.raw = x
13511351

1352+
def test_pickling_subclass(self):
1353+
global MyBufferedIO
1354+
class MyBufferedIO(self.tp):
1355+
def __init__(self, raw, tag):
1356+
super().__init__(raw)
1357+
self.tag = tag
1358+
def __getstate__(self):
1359+
return self.tag, self.raw.getvalue()
1360+
def __setstate__(slf, state):
1361+
tag, value = state
1362+
slf.__init__(self.BytesIO(value), tag)
1363+
1364+
raw = self.BytesIO(b'data')
1365+
buf = MyBufferedIO(raw, tag='ham')
1366+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1367+
with self.subTest(protocol=proto):
1368+
pickled = pickle.dumps(buf, proto)
1369+
newbuf = pickle.loads(pickled)
1370+
self.assertEqual(newbuf.raw.getvalue(), b'data')
1371+
self.assertEqual(newbuf.tag, 'ham')
1372+
del MyBufferedIO
1373+
13521374

13531375
class SizeofTest:
13541376

@@ -3932,6 +3954,28 @@ def test_issue35928(self):
39323954
f.write(res)
39333955
self.assertEqual(res + f.readline(), 'foo\nbar\n')
39343956

3957+
def test_pickling_subclass(self):
3958+
global MyTextIO
3959+
class MyTextIO(self.TextIOWrapper):
3960+
def __init__(self, raw, tag):
3961+
super().__init__(raw)
3962+
self.tag = tag
3963+
def __getstate__(self):
3964+
return self.tag, self.buffer.getvalue()
3965+
def __setstate__(slf, state):
3966+
tag, value = state
3967+
slf.__init__(self.BytesIO(value), tag)
3968+
3969+
raw = self.BytesIO(b'data')
3970+
txt = MyTextIO(raw, 'ham')
3971+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
3972+
with self.subTest(protocol=proto):
3973+
pickled = pickle.dumps(txt, proto)
3974+
newtxt = pickle.loads(pickled)
3975+
self.assertEqual(newtxt.buffer.getvalue(), b'data')
3976+
self.assertEqual(newtxt.tag, 'ham')
3977+
del MyTextIO
3978+
39353979

39363980
class MemviewBytesIO(io.BytesIO):
39373981
'''A BytesIO object whose read method returns memoryviews
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
@@ -2530,8 +2530,7 @@ static PyMethodDef bufferedreader_methods[] = {
25302530
_IO__BUFFERED_TRUNCATE_METHODDEF
25312531
_IO__BUFFERED___SIZEOF___METHODDEF
25322532

2533-
{"__reduce__", _PyIOBase_cannot_pickle, METH_NOARGS},
2534-
{"__reduce_ex__", _PyIOBase_cannot_pickle, METH_O},
2533+
{"__getstate__", _PyIOBase_cannot_pickle, METH_NOARGS},
25352534
{NULL, NULL}
25362535
};
25372536

@@ -2590,8 +2589,7 @@ static PyMethodDef bufferedwriter_methods[] = {
25902589
_IO__BUFFERED_TELL_METHODDEF
25912590
_IO__BUFFERED___SIZEOF___METHODDEF
25922591

2593-
{"__reduce__", _PyIOBase_cannot_pickle, METH_NOARGS},
2594-
{"__reduce_ex__", _PyIOBase_cannot_pickle, METH_O},
2592+
{"__getstate__", _PyIOBase_cannot_pickle, METH_NOARGS},
25952593
{NULL, NULL}
25962594
};
25972595

@@ -2708,8 +2706,7 @@ static PyMethodDef bufferedrandom_methods[] = {
27082706
_IO_BUFFEREDWRITER_WRITE_METHODDEF
27092707
_IO__BUFFERED___SIZEOF___METHODDEF
27102708

2711-
{"__reduce__", _PyIOBase_cannot_pickle, METH_NOARGS},
2712-
{"__reduce_ex__", _PyIOBase_cannot_pickle, METH_O},
2709+
{"__getstate__", _PyIOBase_cannot_pickle, METH_NOARGS},
27132710
{NULL, NULL}
27142711
};
27152712

Modules/_io/fileio.c

+1-2
Original file line numberDiff line numberDiff line change
@@ -1178,8 +1178,7 @@ static PyMethodDef fileio_methods[] = {
11781178
_IO_FILEIO_FILENO_METHODDEF
11791179
_IO_FILEIO_ISATTY_METHODDEF
11801180
{"_dealloc_warn", (PyCFunction)fileio_dealloc_warn, METH_O, NULL},
1181-
{"__reduce__", _PyIOBase_cannot_pickle, METH_NOARGS},
1182-
{"__reduce_ex__", _PyIOBase_cannot_pickle, METH_O},
1181+
{"__getstate__", _PyIOBase_cannot_pickle, METH_NOARGS},
11831182
{NULL, NULL} /* sentinel */
11841183
};
11851184

Modules/_io/textio.c

+1-2
Original file line numberDiff line numberDiff line change
@@ -3350,8 +3350,7 @@ static PyMethodDef textiowrapper_methods[] = {
33503350
_IO_TEXTIOWRAPPER_TELL_METHODDEF
33513351
_IO_TEXTIOWRAPPER_TRUNCATE_METHODDEF
33523352

3353-
{"__reduce__", _PyIOBase_cannot_pickle, METH_NOARGS},
3354-
{"__reduce_ex__", _PyIOBase_cannot_pickle, METH_O},
3353+
{"__getstate__", _PyIOBase_cannot_pickle, METH_NOARGS},
33553354
{NULL, NULL}
33563355
};
33573356

0 commit comments

Comments
 (0)