summaryrefslogtreecommitdiffstats
path: root/Modules/_io
diff options
context:
space:
mode:
authorAntoine Pitrou <solipsis@pitrou.net>2009-10-24 12:23:18 (GMT)
committerAntoine Pitrou <solipsis@pitrou.net>2009-10-24 12:23:18 (GMT)
commitfa94e80f3b2c88d4cdcc809c8e1931e289373e84 (patch)
treeb333dc0a53e75f15d346e19000489c0af27f5982 /Modules/_io
parentfd42f3099112b8de50c9145af4a43c1cf35887a3 (diff)
downloadcpython-fa94e80f3b2c88d4cdcc809c8e1931e289373e84.zip
cpython-fa94e80f3b2c88d4cdcc809c8e1931e289373e84.tar.gz
cpython-fa94e80f3b2c88d4cdcc809c8e1931e289373e84.tar.bz2
Manual py3k backport: [svn r74158] Issue #6218: Make io.BytesIO and io.StringIO picklable.
Diffstat (limited to 'Modules/_io')
-rw-r--r--Modules/_io/bytesio.c122
-rw-r--r--Modules/_io/stringio.c147
2 files changed, 260 insertions, 9 deletions
diff --git a/Modules/_io/bytesio.c b/Modules/_io/bytesio.c
index f477550..beba11c 100644
--- a/Modules/_io/bytesio.c
+++ b/Modules/_io/bytesio.c
@@ -613,6 +613,120 @@ bytesio_close(bytesio *self)
Py_RETURN_NONE;
}
+/* Pickling support.
+
+ Note that only pickle protocol 2 and onward are supported since we use
+ extended __reduce__ API of PEP 307 to make BytesIO instances picklable.
+
+ Providing support for protocol < 2 would require the __reduce_ex__ method
+ which is notably long-winded when defined properly.
+
+ For BytesIO, the implementation would similar to one coded for
+ object.__reduce_ex__, but slightly less general. To be more specific, we
+ could call bytesio_getstate directly and avoid checking for the presence of
+ a fallback __reduce__ method. However, we would still need a __newobj__
+ function to use the efficient instance representation of PEP 307.
+ */
+
+static PyObject *
+bytesio_getstate(bytesio *self)
+{
+ PyObject *initvalue = bytesio_getvalue(self);
+ PyObject *dict;
+ PyObject *state;
+
+ if (initvalue == NULL)
+ return NULL;
+ if (self->dict == NULL) {
+ Py_INCREF(Py_None);
+ dict = Py_None;
+ }
+ else {
+ dict = PyDict_Copy(self->dict);
+ if (dict == NULL)
+ return NULL;
+ }
+
+ state = Py_BuildValue("(OnN)", initvalue, self->pos, dict);
+ Py_DECREF(initvalue);
+ return state;
+}
+
+static PyObject *
+bytesio_setstate(bytesio *self, PyObject *state)
+{
+ PyObject *result;
+ PyObject *position_obj;
+ PyObject *dict;
+ Py_ssize_t pos;
+
+ assert(state != NULL);
+
+ /* We allow the state tuple to be longer than 3, because we may need
+ someday to extend the object's state without breaking
+ backward-compatibility. */
+ if (!PyTuple_Check(state) || Py_SIZE(state) < 3) {
+ PyErr_Format(PyExc_TypeError,
+ "%.200s.__setstate__ argument should be 3-tuple, got %.200s",
+ Py_TYPE(self)->tp_name, Py_TYPE(state)->tp_name);
+ return NULL;
+ }
+ /* Reset the object to its default state. This is only needed to handle
+ the case of repeated calls to __setstate__. */
+ self->string_size = 0;
+ self->pos = 0;
+
+ /* Set the value of the internal buffer. If state[0] does not support the
+ buffer protocol, bytesio_write will raise the appropriate TypeError. */
+ result = bytesio_write(self, PyTuple_GET_ITEM(state, 0));
+ if (result == NULL)
+ return NULL;
+ Py_DECREF(result);
+
+ /* Set carefully the position value. Alternatively, we could use the seek
+ method instead of modifying self->pos directly to better protect the
+ object internal state against errneous (or malicious) inputs. */
+ position_obj = PyTuple_GET_ITEM(state, 1);
+ if (!PyIndex_Check(position_obj)) {
+ PyErr_Format(PyExc_TypeError,
+ "second item of state must be an integer, not %.200s",
+ Py_TYPE(position_obj)->tp_name);
+ return NULL;
+ }
+ pos = PyNumber_AsSsize_t(position_obj, PyExc_OverflowError);
+ if (pos == -1 && PyErr_Occurred())
+ return NULL;
+ if (pos < 0) {
+ PyErr_SetString(PyExc_ValueError,
+ "position value cannot be negative");
+ return NULL;
+ }
+ self->pos = pos;
+
+ /* Set the dictionary of the instance variables. */
+ dict = PyTuple_GET_ITEM(state, 2);
+ if (dict != Py_None) {
+ if (!PyDict_Check(dict)) {
+ PyErr_Format(PyExc_TypeError,
+ "third item of state should be a dict, got a %.200s",
+ Py_TYPE(dict)->tp_name);
+ return NULL;
+ }
+ if (self->dict) {
+ /* Alternatively, we could replace the internal dictionary
+ completely. However, it seems more practical to just update it. */
+ if (PyDict_Update(self->dict, dict) < 0)
+ return NULL;
+ }
+ else {
+ Py_INCREF(dict);
+ self->dict = dict;
+ }
+ }
+
+ Py_RETURN_NONE;
+}
+
static void
bytesio_dealloc(bytesio *self)
{
@@ -637,9 +751,9 @@ bytesio_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
if (self == NULL)
return NULL;
- self->string_size = 0;
- self->pos = 0;
- self->buf_size = 0;
+ /* tp_alloc initializes all the fields to zero. So we don't have to
+ initialize them here. */
+
self->buf = (char *)PyMem_Malloc(0);
if (self->buf == NULL) {
Py_DECREF(self);
@@ -712,6 +826,8 @@ static struct PyMethodDef bytesio_methods[] = {
{"getvalue", (PyCFunction)bytesio_getvalue, METH_VARARGS, getval_doc},
{"seek", (PyCFunction)bytesio_seek, METH_VARARGS, seek_doc},
{"truncate", (PyCFunction)bytesio_truncate, METH_VARARGS, truncate_doc},
+ {"__getstate__", (PyCFunction)bytesio_getstate, METH_NOARGS, NULL},
+ {"__setstate__", (PyCFunction)bytesio_setstate, METH_O, NULL},
{NULL, NULL} /* sentinel */
};
diff --git a/Modules/_io/stringio.c b/Modules/_io/stringio.c
index 95180a9..0fc8078 100644
--- a/Modules/_io/stringio.c
+++ b/Modules/_io/stringio.c
@@ -539,9 +539,9 @@ stringio_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
if (self == NULL)
return NULL;
- self->string_size = 0;
- self->pos = 0;
- self->buf_size = 0;
+ /* tp_alloc initializes all the fields to zero. So we don't have to
+ initialize them here. */
+
self->buf = (Py_UNICODE *)PyMem_Malloc(0);
if (self->buf == NULL) {
Py_DECREF(self);
@@ -571,8 +571,8 @@ stringio_init(stringio *self, PyObject *args, PyObject *kwds)
return -1;
}
if (value && value != Py_None && !PyUnicode_Check(value)) {
- PyErr_Format(PyExc_ValueError,
- "initial_value must be str or None, not %.200s",
+ PyErr_Format(PyExc_TypeError,
+ "initial_value must be unicode or None, not %.200s",
Py_TYPE(value)->tp_name);
return -1;
}
@@ -583,6 +583,9 @@ stringio_init(stringio *self, PyObject *args, PyObject *kwds)
Py_CLEAR(self->writenl);
Py_CLEAR(self->decoder);
+ assert((newline != NULL && newline_obj != Py_None) ||
+ (newline == NULL && newline_obj == Py_None));
+
if (newline) {
self->readnl = PyString_FromString(newline);
if (self->readnl == NULL)
@@ -654,6 +657,135 @@ stringio_writable(stringio *self, PyObject *args)
Py_RETURN_TRUE;
}
+/* Pickling support.
+
+ The implementation of __getstate__ is similar to the one for BytesIO,
+ except that we also save the newline parameter. For __setstate__ and unlike
+ BytesIO, we call __init__ to restore the object's state. Doing so allows us
+ to avoid decoding the complex newline state while keeping the object
+ representation compact.
+
+ See comment in bytesio.c regarding why only pickle protocols and onward are
+ supported.
+*/
+
+static PyObject *
+stringio_getstate(stringio *self)
+{
+ PyObject *initvalue = stringio_getvalue(self);
+ PyObject *dict;
+ PyObject *state;
+
+ if (initvalue == NULL)
+ return NULL;
+ if (self->dict == NULL) {
+ Py_INCREF(Py_None);
+ dict = Py_None;
+ }
+ else {
+ dict = PyDict_Copy(self->dict);
+ if (dict == NULL)
+ return NULL;
+ }
+
+ state = Py_BuildValue("(OOnN)", initvalue,
+ self->readnl ? self->readnl : Py_None,
+ self->pos, dict);
+ Py_DECREF(initvalue);
+ return state;
+}
+
+static PyObject *
+stringio_setstate(stringio *self, PyObject *state)
+{
+ PyObject *initarg;
+ PyObject *position_obj;
+ PyObject *dict;
+ Py_ssize_t pos;
+
+ assert(state != NULL);
+ CHECK_CLOSED(self);
+
+ /* We allow the state tuple to be longer than 4, because we may need
+ someday to extend the object's state without breaking
+ backward-compatibility. */
+ if (!PyTuple_Check(state) || Py_SIZE(state) < 4) {
+ PyErr_Format(PyExc_TypeError,
+ "%.200s.__setstate__ argument should be 4-tuple, got %.200s",
+ Py_TYPE(self)->tp_name, Py_TYPE(state)->tp_name);
+ return NULL;
+ }
+
+ /* Initialize the object's state. */
+ initarg = PyTuple_GetSlice(state, 0, 2);
+ if (initarg == NULL)
+ return NULL;
+ if (stringio_init(self, initarg, NULL) < 0) {
+ Py_DECREF(initarg);
+ return NULL;
+ }
+ Py_DECREF(initarg);
+
+ /* Restore the buffer state. Even if __init__ did initialize the buffer,
+ we have to initialize it again since __init__ may translates the
+ newlines in the inital_value string. We clearly do not want that
+ because the string value in the state tuple has already been translated
+ once by __init__. So we do not take any chance and replace object's
+ buffer completely. */
+ {
+ Py_UNICODE *buf = PyUnicode_AS_UNICODE(PyTuple_GET_ITEM(state, 0));
+ Py_ssize_t bufsize = PyUnicode_GET_SIZE(PyTuple_GET_ITEM(state, 0));
+ if (resize_buffer(self, bufsize) < 0)
+ return NULL;
+ memcpy(self->buf, buf, bufsize * sizeof(Py_UNICODE));
+ self->string_size = bufsize;
+ }
+
+ /* Set carefully the position value. Alternatively, we could use the seek
+ method instead of modifying self->pos directly to better protect the
+ object internal state against errneous (or malicious) inputs. */
+ position_obj = PyTuple_GET_ITEM(state, 2);
+ if (!PyIndex_Check(position_obj)) {
+ PyErr_Format(PyExc_TypeError,
+ "third item of state must be an integer, got %.200s",
+ Py_TYPE(position_obj)->tp_name);
+ return NULL;
+ }
+ pos = PyNumber_AsSsize_t(position_obj, PyExc_OverflowError);
+ if (pos == -1 && PyErr_Occurred())
+ return NULL;
+ if (pos < 0) {
+ PyErr_SetString(PyExc_ValueError,
+ "position value cannot be negative");
+ return NULL;
+ }
+ self->pos = pos;
+
+ /* Set the dictionary of the instance variables. */
+ dict = PyTuple_GET_ITEM(state, 3);
+ if (dict != Py_None) {
+ if (!PyDict_Check(dict)) {
+ PyErr_Format(PyExc_TypeError,
+ "fourth item of state should be a dict, got a %.200s",
+ Py_TYPE(dict)->tp_name);
+ return NULL;
+ }
+ if (self->dict) {
+ /* Alternatively, we could replace the internal dictionary
+ completely. However, it seems more practical to just update it. */
+ if (PyDict_Update(self->dict, dict) < 0)
+ return NULL;
+ }
+ else {
+ Py_INCREF(dict);
+ self->dict = dict;
+ }
+ }
+
+ Py_RETURN_NONE;
+}
+
+
static PyObject *
stringio_closed(stringio *self, void *context)
{
@@ -688,10 +820,13 @@ static struct PyMethodDef stringio_methods[] = {
{"truncate", (PyCFunction)stringio_truncate, METH_VARARGS, stringio_truncate_doc},
{"seek", (PyCFunction)stringio_seek, METH_VARARGS, stringio_seek_doc},
{"write", (PyCFunction)stringio_write, METH_O, stringio_write_doc},
-
+
{"seekable", (PyCFunction)stringio_seekable, METH_NOARGS},
{"readable", (PyCFunction)stringio_readable, METH_NOARGS},
{"writable", (PyCFunction)stringio_writable, METH_NOARGS},
+
+ {"__getstate__", (PyCFunction)stringio_getstate, METH_NOARGS},
+ {"__setstate__", (PyCFunction)stringio_setstate, METH_O},
{NULL, NULL} /* sentinel */
};