Skip to content

Commit b623a25

Browse files
committed
Documentation on pickling pretty much complete.
1 parent 344b5c4 commit b623a25

File tree

1 file changed

+95
-14
lines changed

1 file changed

+95
-14
lines changed

doc/sphinx/source/pickle.rst

+95-14
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
:maxdepth: 2
66

77
====================================
8-
Pickling and C Extensions
8+
Pickling and C Extension Types
99
====================================
1010

1111
If you need to provide support for pickling your specialised types from your C extension then you need to implement some special functions.
@@ -44,7 +44,7 @@ Implementing ``__getstate__``
4444
Note that a ``Custom`` object has two Python objects (``first`` and ``last``) and a C integer (``number``) that need to be converted to a Python object.
4545
We also need to add the version information.
4646

47-
Her is the C implementation:
47+
Here is the C implementation:
4848

4949
.. code-block:: c
5050
@@ -100,6 +100,11 @@ Before setting any member value we need to de-allocate the existing value set by
100100
pickle_version, PICKLE_VERSION);
101101
return NULL;
102102
}
103+
104+
Set ``first``:
105+
106+
.. code-block:: c
107+
103108
/* NOTE: Custom_new() will have been invoked so self->first and self->last
104109
* will have been allocated so we have to de-allocate them. */
105110
Py_DECREF(self->first);
@@ -112,6 +117,10 @@ Before setting any member value we need to de-allocate the existing value set by
112117
/* Increment the borrowed reference for our instance of it. */
113118
Py_INCREF(self->first);
114119
120+
Set ``last``:
121+
122+
.. code-block:: c
123+
115124
/* Similar to self->first above. */
116125
Py_DECREF(self->last);
117126
self->last = PyDict_GetItemString(state, "last"); /* Borrowed reference. */
@@ -122,6 +131,10 @@ Before setting any member value we need to de-allocate the existing value set by
122131
}
123132
Py_INCREF(self->last);
124133
134+
Set ``number``, this is a C fundamental type:
135+
136+
.. code-block:: c
137+
125138
/* Borrowed reference but no need to incref as we create a C long from it. */
126139
PyObject *number = PyDict_GetItemString(state, "number");
127140
if (number == NULL) {
@@ -131,9 +144,79 @@ Before setting any member value we need to de-allocate the existing value set by
131144
}
132145
self->number = (int) PyLong_AsLong(number);
133146
147+
And we are done.
148+
149+
.. code-block:: c
150+
134151
Py_RETURN_NONE;
135152
}
136153
154+
The complete code is:
155+
156+
.. code-block:: c
157+
158+
/* Un-pickle the object */
159+
static PyObject *
160+
Custom___setstate__(CustomObject *self, PyObject *state) {
161+
/* Error check. */
162+
if (!PyDict_CheckExact(state)) {
163+
PyErr_SetString(PyExc_ValueError, "Pickled object is not a dict.");
164+
return NULL;
165+
}
166+
/* Version check. */
167+
/* Borrowed reference but no need to increment as we create a C long
168+
* from it. */
169+
PyObject *temp = PyDict_GetItemString(state, PICKLE_VERSION_KEY);
170+
if (temp == NULL) {
171+
/* PyDict_GetItemString does not set any error state so we have to. */
172+
PyErr_Format(PyExc_KeyError, "No \"%s\" in pickled dict.",
173+
PICKLE_VERSION_KEY);
174+
return NULL;
175+
}
176+
int pickle_version = (int) PyLong_AsLong(temp);
177+
if (pickle_version != PICKLE_VERSION) {
178+
PyErr_Format(PyExc_ValueError,
179+
"Pickle version mismatch. Got version %d but expected version %d.",
180+
pickle_version, PICKLE_VERSION);
181+
return NULL;
182+
}
183+
184+
/* NOTE: Custom_new() will have been invoked so self->first and self->last
185+
* will have been allocated so we have to de-allocate them. */
186+
Py_DECREF(self->first);
187+
self->first = PyDict_GetItemString(state, "first"); /* Borrowed reference. */
188+
if (self->first == NULL) {
189+
/* PyDict_GetItemString does not set any error state so we have to. */
190+
PyErr_SetString(PyExc_KeyError, "No \"first\" in pickled dict.");
191+
return NULL;
192+
}
193+
/* Increment the borrowed reference for our instance of it. */
194+
Py_INCREF(self->first);
195+
196+
/* Similar to self->first above. */
197+
Py_DECREF(self->last);
198+
self->last = PyDict_GetItemString(state, "last"); /* Borrowed reference. */
199+
if (self->last == NULL) {
200+
/* PyDict_GetItemString does not set any error state so we have to. */
201+
PyErr_SetString(PyExc_KeyError, "No \"last\" in pickled dict.");
202+
return NULL;
203+
}
204+
Py_INCREF(self->last);
205+
206+
/* Borrowed reference but no need to incref as we create a C long from it. */
207+
PyObject *number = PyDict_GetItemString(state, "number");
208+
if (number == NULL) {
209+
/* PyDict_GetItemString does not set any error state so we have to. */
210+
PyErr_SetString(PyExc_KeyError, "No \"number\" in pickled dict.");
211+
return NULL;
212+
}
213+
self->number = (int) PyLong_AsLong(number);
214+
215+
Py_RETURN_NONE;
216+
}
217+
218+
219+
137220
Add the Special Methods
138221
---------------------------------
139222

@@ -167,32 +250,30 @@ Here is some Python code that exercises our module:
167250
import custom2
168251
169252
original = custom2.Custom('FIRST', 'LAST', 11)
170-
print(
171-
f'original is {original} @ 0x{id(original):x} first: {original.first} last: {original.last}'
172-
' number: {original.number} name: {original.name()}'
173-
)
253+
print(f'original is {original} @ 0x{id(original):x}')
254+
print(f'original first: {original.first} last: {original.last} number: {original.number} name: {original.name()}')
174255
pickled_value = pickle.dumps(original)
175256
print(f'Pickled original is {pickled_value}')
176257
result = pickle.loads(pickled_value)
177-
print(
178-
f'result is {result} @ 0x{id(result):x} first: {result.first} last: {result.last}'
179-
' number: {result.number} name: {result.name()}'
180-
)
181-
258+
print(f'result is {result} @ 0x{id(result):x}')
259+
print(f'result first: {result.first} last: {result.last} number: {result.number} name: {result.name()}')
182260
183261
.. code-block:: sh
184262
185263
$ python main.py
186-
original is <custom2.Custom object at 0x102b00810> @ 0x102b00810 first: FIRST last: LAST number: 11 name: FIRST LAST
264+
original is <custom2.Custom object at 0x1049e6810> @ 0x1049e6810
265+
original first: FIRST last: LAST number: 11 name: FIRST LAST
187266
Pickled original is b'\x80\x04\x95[\x00\x00\x00\x00\x00\x00\x00\x8c\x07custom2\x94\x8c\x06Custom\x94\x93\x94)\x81\x94}\x94(\x8c\x05first\x94\x8c\x05FIRST\x94\x8c\x04last\x94\x8c\x04LAST\x94\x8c\x06number\x94K\x0b\x8c\x0f_pickle_version\x94K\x01ub.'
188-
result is <custom2.Custom object at 0x102a3f510> @ 0x102a3f510 first: FIRST last: LAST number: 11 name: FIRST LAST
267+
result is <custom2.Custom object at 0x1049252d0> @ 0x1049252d0
268+
result first: FIRST last: LAST number: 11 name: FIRST LAST
189269
190-
So we have pickled one object and recreated a different, but equivalent, instance from that object.
270+
So we have pickled one object and recreated a different, but equivalent, instance from the pickle of the original object which is what we set out to do.
191271

192272
Pickling Objects with External State
193273
-----------------------------------------
194274

195275
This is just a simple example, if your object relies on external state such as open files, databases and the like you need to be careful, and knowledgeable about your state management.
276+
There is some useful information here: `Handling Stateful Objects <https://docs.python.org/3/library/pickle.html#pickle-state>`_
196277

197278
References
198279
-----------------------

0 commit comments

Comments
 (0)