Skip to content

Commit 5bbd231

Browse files
Issue python#15513: Added a __sizeof__ implementation for pickle classes.
1 parent 05dadcf commit 5bbd231

4 files changed

Lines changed: 184 additions & 4 deletions

File tree

Lib/test/test_pickle.py

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import pickle
22
import io
33
import collections
4+
import struct
5+
import sys
46

7+
import unittest
58
from test import support
69

710
from test.pickletester import AbstractPickleTests
@@ -138,6 +141,71 @@ class CChainDispatchTableTests(AbstractDispatchTableTests):
138141
def get_dispatch_table(self):
139142
return collections.ChainMap({}, pickle.dispatch_table)
140143

144+
@support.cpython_only
145+
class SizeofTests(unittest.TestCase):
146+
check_sizeof = support.check_sizeof
147+
148+
def test_pickler(self):
149+
basesize = support.calcobjsize('5P2n3i2n3iP')
150+
p = _pickle.Pickler(io.BytesIO())
151+
self.assertEqual(object.__sizeof__(p), basesize)
152+
MT_size = struct.calcsize('3nP0n')
153+
ME_size = struct.calcsize('Pn0P')
154+
check = self.check_sizeof
155+
check(p, basesize +
156+
MT_size + 8 * ME_size + # Minimal memo table size.
157+
sys.getsizeof(b'x'*4096)) # Minimal write buffer size.
158+
for i in range(6):
159+
p.dump(chr(i))
160+
check(p, basesize +
161+
MT_size + 32 * ME_size + # Size of memo table required to
162+
# save references to 6 objects.
163+
0) # Write buffer is cleared after every dump().
164+
165+
def test_unpickler(self):
166+
basesize = support.calcobjsize('2Pn2P 2P2n2i5P 2P3n6P2n2i')
167+
unpickler = _pickle.Unpickler
168+
P = struct.calcsize('P') # Size of memo table entry.
169+
n = struct.calcsize('n') # Size of mark table entry.
170+
check = self.check_sizeof
171+
for encoding in 'ASCII', 'UTF-16', 'latin-1':
172+
for errors in 'strict', 'replace':
173+
u = unpickler(io.BytesIO(),
174+
encoding=encoding, errors=errors)
175+
self.assertEqual(object.__sizeof__(u), basesize)
176+
check(u, basesize +
177+
32 * P + # Minimal memo table size.
178+
len(encoding) + 1 + len(errors) + 1)
179+
180+
stdsize = basesize + len('ASCII') + 1 + len('strict') + 1
181+
def check_unpickler(data, memo_size, marks_size):
182+
dump = pickle.dumps(data)
183+
u = unpickler(io.BytesIO(dump),
184+
encoding='ASCII', errors='strict')
185+
u.load()
186+
check(u, stdsize + memo_size * P + marks_size * n)
187+
188+
check_unpickler(0, 32, 0)
189+
# 20 is minimal non-empty mark stack size.
190+
check_unpickler([0] * 100, 32, 20)
191+
# 128 is memo table size required to save references to 100 objects.
192+
check_unpickler([chr(i) for i in range(100)], 128, 20)
193+
def recurse(deep):
194+
data = 0
195+
for i in range(deep):
196+
data = [data, data]
197+
return data
198+
check_unpickler(recurse(0), 32, 0)
199+
check_unpickler(recurse(1), 32, 20)
200+
check_unpickler(recurse(20), 32, 58)
201+
check_unpickler(recurse(50), 64, 58)
202+
check_unpickler(recurse(100), 128, 134)
203+
204+
u = unpickler(io.BytesIO(pickle.dumps('a', 0)),
205+
encoding='ASCII', errors='strict')
206+
u.load()
207+
check(u, stdsize + 32 * P + 2 + 1)
208+
141209

142210
def test_main():
143211
tests = [PickleTests, PyPicklerTests, PyPersPicklerTests,
@@ -148,7 +216,7 @@ def test_main():
148216
PyPicklerUnpicklerObjectTests,
149217
CPicklerUnpicklerObjectTests,
150218
CDispatchTableTests, CChainDispatchTableTests,
151-
InMemoryPickleTests])
219+
InMemoryPickleTests, SizeofTests])
152220
support.run_unittest(*tests)
153221
support.run_doctest(pickle)
154222

Misc/NEWS

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ Core and Builtins
4141
Library
4242
-------
4343

44-
- Issue #19858: pickletools.optimize() now aware of the MEMOIZE opcode, can
44+
- Issue #15513: Added a __sizeof__ implementation for pickle classes.
45+
46+
- Issue #19858: pickletools.optimize() now aware of the MEMOIZE opcode, can
4547
produce more compact result and no longer produces invalid output if input
4648
data contains MEMOIZE opcodes together with PUT or BINPUT opcodes.
4749

Modules/_pickle.c

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ static PyTypeObject Pdata_Type = {
375375
PyVarObject_HEAD_INIT(NULL, 0)
376376
"_pickle.Pdata", /*tp_name*/
377377
sizeof(Pdata), /*tp_basicsize*/
378-
0, /*tp_itemsize*/
378+
sizeof(PyObject *), /*tp_itemsize*/
379379
(destructor)Pdata_dealloc, /*tp_dealloc*/
380380
};
381381

@@ -3930,9 +3930,37 @@ _pickle_Pickler_dump(PicklerObject *self, PyObject *obj)
39303930
Py_RETURN_NONE;
39313931
}
39323932

3933+
/*[clinic input]
3934+
3935+
_pickle.Pickler.__sizeof__ -> Py_ssize_t
3936+
3937+
Returns size in memory, in bytes.
3938+
[clinic start generated code]*/
3939+
3940+
static Py_ssize_t
3941+
_pickle_Pickler___sizeof___impl(PicklerObject *self)
3942+
/*[clinic end generated code: output=106edb3123f332e1 input=8cbbec9bd5540d42]*/
3943+
{
3944+
Py_ssize_t res, s;
3945+
3946+
res = sizeof(PicklerObject);
3947+
if (self->memo != NULL) {
3948+
res += sizeof(PyMemoTable);
3949+
res += self->memo->mt_allocated * sizeof(PyMemoEntry);
3950+
}
3951+
if (self->output_buffer != NULL) {
3952+
s = _PySys_GetSizeOf(self->output_buffer);
3953+
if (s == -1)
3954+
return -1;
3955+
res += s;
3956+
}
3957+
return res;
3958+
}
3959+
39333960
static struct PyMethodDef Pickler_methods[] = {
39343961
_PICKLE_PICKLER_DUMP_METHODDEF
39353962
_PICKLE_PICKLER_CLEAR_MEMO_METHODDEF
3963+
_PICKLE_PICKLER___SIZEOF___METHODDEF
39363964
{NULL, NULL} /* sentinel */
39373965
};
39383966

@@ -6289,9 +6317,37 @@ _pickle_Unpickler_find_class_impl(UnpicklerObject *self, PyObject *module_name,
62896317
return global;
62906318
}
62916319

6320+
/*[clinic input]
6321+
6322+
_pickle.Unpickler.__sizeof__ -> Py_ssize_t
6323+
6324+
Returns size in memory, in bytes.
6325+
[clinic start generated code]*/
6326+
6327+
static Py_ssize_t
6328+
_pickle_Unpickler___sizeof___impl(UnpicklerObject *self)
6329+
/*[clinic end generated code: output=119d9d03ad4c7651 input=13333471fdeedf5e]*/
6330+
{
6331+
Py_ssize_t res;
6332+
6333+
res = sizeof(UnpicklerObject);
6334+
if (self->memo != NULL)
6335+
res += self->memo_size * sizeof(PyObject *);
6336+
if (self->marks != NULL)
6337+
res += self->marks_size * sizeof(Py_ssize_t);
6338+
if (self->input_line != NULL)
6339+
res += strlen(self->input_line) + 1;
6340+
if (self->encoding != NULL)
6341+
res += strlen(self->encoding) + 1;
6342+
if (self->errors != NULL)
6343+
res += strlen(self->errors) + 1;
6344+
return res;
6345+
}
6346+
62926347
static struct PyMethodDef Unpickler_methods[] = {
62936348
_PICKLE_UNPICKLER_LOAD_METHODDEF
62946349
_PICKLE_UNPICKLER_FIND_CLASS_METHODDEF
6350+
_PICKLE_UNPICKLER___SIZEOF___METHODDEF
62956351
{NULL, NULL} /* sentinel */
62966352
};
62976353

Modules/clinic/_pickle.c.h

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,33 @@ PyDoc_STRVAR(_pickle_Pickler_dump__doc__,
3434
#define _PICKLE_PICKLER_DUMP_METHODDEF \
3535
{"dump", (PyCFunction)_pickle_Pickler_dump, METH_O, _pickle_Pickler_dump__doc__},
3636

37+
PyDoc_STRVAR(_pickle_Pickler___sizeof____doc__,
38+
"__sizeof__($self, /)\n"
39+
"--\n"
40+
"\n"
41+
"Returns size in memory, in bytes.");
42+
43+
#define _PICKLE_PICKLER___SIZEOF___METHODDEF \
44+
{"__sizeof__", (PyCFunction)_pickle_Pickler___sizeof__, METH_NOARGS, _pickle_Pickler___sizeof____doc__},
45+
46+
static Py_ssize_t
47+
_pickle_Pickler___sizeof___impl(PicklerObject *self);
48+
49+
static PyObject *
50+
_pickle_Pickler___sizeof__(PicklerObject *self, PyObject *Py_UNUSED(ignored))
51+
{
52+
PyObject *return_value = NULL;
53+
Py_ssize_t _return_value;
54+
55+
_return_value = _pickle_Pickler___sizeof___impl(self);
56+
if ((_return_value == -1) && PyErr_Occurred())
57+
goto exit;
58+
return_value = PyLong_FromSsize_t(_return_value);
59+
60+
exit:
61+
return return_value;
62+
}
63+
3764
PyDoc_STRVAR(_pickle_Pickler___init____doc__,
3865
"Pickler(file, protocol=None, fix_imports=True)\n"
3966
"--\n"
@@ -191,6 +218,33 @@ _pickle_Unpickler_find_class(UnpicklerObject *self, PyObject *args)
191218
return return_value;
192219
}
193220

221+
PyDoc_STRVAR(_pickle_Unpickler___sizeof____doc__,
222+
"__sizeof__($self, /)\n"
223+
"--\n"
224+
"\n"
225+
"Returns size in memory, in bytes.");
226+
227+
#define _PICKLE_UNPICKLER___SIZEOF___METHODDEF \
228+
{"__sizeof__", (PyCFunction)_pickle_Unpickler___sizeof__, METH_NOARGS, _pickle_Unpickler___sizeof____doc__},
229+
230+
static Py_ssize_t
231+
_pickle_Unpickler___sizeof___impl(UnpicklerObject *self);
232+
233+
static PyObject *
234+
_pickle_Unpickler___sizeof__(UnpicklerObject *self, PyObject *Py_UNUSED(ignored))
235+
{
236+
PyObject *return_value = NULL;
237+
Py_ssize_t _return_value;
238+
239+
_return_value = _pickle_Unpickler___sizeof___impl(self);
240+
if ((_return_value == -1) && PyErr_Occurred())
241+
goto exit;
242+
return_value = PyLong_FromSsize_t(_return_value);
243+
244+
exit:
245+
return return_value;
246+
}
247+
194248
PyDoc_STRVAR(_pickle_Unpickler___init____doc__,
195249
"Unpickler(file, *, fix_imports=True, encoding=\'ASCII\', errors=\'strict\')\n"
196250
"--\n"
@@ -488,4 +542,4 @@ _pickle_loads(PyModuleDef *module, PyObject *args, PyObject *kwargs)
488542
exit:
489543
return return_value;
490544
}
491-
/*[clinic end generated code: output=f965b6c7018c898d input=a9049054013a1b77]*/
545+
/*[clinic end generated code: output=3aba79576e240c62 input=a9049054013a1b77]*/

0 commit comments

Comments
 (0)