Index: Python/bltinmodule.c =================================================================== --- Python/bltinmodule.c (revision 63363) +++ Python/bltinmodule.c (working copy) @@ -1310,7 +1310,22 @@ \n\ Return the number of items of a sequence or mapping."); +static PyObject * +builtin_footprint(PyObject *self, PyObject *o) +{ + Py_ssize_t res; + + res = PyObject_Footprint(o); + if (res < 0 && PyErr_Occurred()) + return NULL; + return PyInt_FromSsize_t(res); +} +PyDoc_STRVAR(footprint_doc, +"footprint(object) -> integer\n\ +\n\ +Return the memory footprint an object in bytes."); + static PyObject * builtin_locals(PyObject *self) { @@ -2550,6 +2565,7 @@ {"issubclass", builtin_issubclass, METH_VARARGS, issubclass_doc}, {"iter", builtin_iter, METH_VARARGS, iter_doc}, {"len", builtin_len, METH_O, len_doc}, + {"footprint", builtin_footprint, METH_O, footprint_doc}, {"locals", (PyCFunction)builtin_locals, METH_NOARGS, locals_doc}, {"map", builtin_map, METH_VARARGS, map_doc}, {"max", (PyCFunction)builtin_max, METH_VARARGS | METH_KEYWORDS, max_doc}, Index: Include/abstract.h =================================================================== --- Include/abstract.h (revision 63363) +++ Include/abstract.h (working copy) @@ -418,6 +418,13 @@ equivalent to the Python expression: type(o). */ + PyAPI_FUNC(Py_ssize_t) PyObject_Footprint(PyObject *o); + + /* + Return the memory footprint (i.e. size in bytes) of object o or NULL + on failure. + */ + PyAPI_FUNC(Py_ssize_t) PyObject_Size(PyObject *o); /* Index: Include/object.h =================================================================== --- Include/object.h (revision 63363) +++ Include/object.h (working copy) @@ -328,6 +328,7 @@ typedef int (*initproc)(PyObject *, PyObject *, PyObject *); typedef PyObject *(*newfunc)(struct _typeobject *, PyObject *, PyObject *); typedef PyObject *(*allocfunc)(struct _typeobject *, Py_ssize_t); +typedef Py_ssize_t (*footprintfunc)(PyObject *); typedef struct _typeobject { PyObject_VAR_HEAD @@ -408,6 +409,9 @@ /* Type attribute cache version tag. Added in version 2.6 */ unsigned int tp_version_tag; + /* Memory footprint. Added in version 2.6 */ + footprintfunc tp_footprint; + #ifdef COUNT_ALLOCS /* these must be last and never explicitly initialized */ Py_ssize_t tp_allocs; Index: Objects/abstract.c =================================================================== --- Objects/abstract.c (revision 63363) +++ Objects/abstract.c (working copy) @@ -58,6 +58,36 @@ } Py_ssize_t +PyObject_Footprint(PyObject *o) +{ + Py_ssize_t res; + Py_ssize_t size; + footprintfunc m; + + if (o == NULL) { + null_error(); + return -1; + } + + m = o->ob_type->tp_footprint; + if (m) + return o->ob_type->tp_footprint(o); + + res = 0; + size = o->ob_type->tp_itemsize; + if (size > 0) { + Py_ssize_t len; + len = PyObject_Size(o); + if (len == -1 && PyErr_Occurred()) + return -1; + if (len) + res += len * size; + } + res += o->ob_type->tp_basicsize; + return res; +} + +Py_ssize_t PyObject_Size(PyObject *o) { PySequenceMethods *m; Index: Objects/dictobject.c =================================================================== --- Objects/dictobject.c (revision 63363) +++ Objects/dictobject.c (working copy) @@ -2194,6 +2194,18 @@ return dict_update_common(self, args, kwds, "dict"); } +static Py_ssize_t +dict_footprint(PyDictObject *mp) +{ + Py_ssize_t res; + + res = sizeof(PyDictObject) + sizeof(mp->ma_table); + if (mp->ma_table == mp->ma_smalltable) + return res; + else + return res + (mp->ma_mask + 1) * sizeof(PyDictEntry); +} + static PyObject * dict_iter(PyDictObject *dict) { @@ -2252,6 +2264,15 @@ PyType_GenericAlloc, /* tp_alloc */ dict_new, /* tp_new */ PyObject_GC_Del, /* tp_free */ + 0, /* tp_is_gc */ + 0, /* tp_bases */ + 0, /* tp_mro */ + 0, /* tp_cache */ + 0, /* tp_subclasses */ + 0, /* tp_weaklist */ + 0, /* tp_del */ + 0, /* tp_version_tag */ + (footprintfunc)dict_footprint, /* tp_footprint */ }; /* For backward compatibility with old dictionary interface */ Index: Doc/c-api/object.rst =================================================================== --- Doc/c-api/object.rst (revision 63363) +++ Doc/c-api/object.rst (working copy) @@ -304,6 +304,13 @@ .. versionadded:: 2.2 +.. cfunction:: int PyObject_Footprint(PyObject *o) + + Return the memory footprint (i.e. size in bytes) of object o or NULL on failure. + + .. versionadded:: 2.6 + + .. cfunction:: Py_ssize_t PyObject_Length(PyObject *o) Py_ssize_t PyObject_Size(PyObject *o) Index: Doc/c-api/typeobj.rst =================================================================== --- Doc/c-api/typeobj.rst (revision 63363) +++ Doc/c-api/typeobj.rst (working copy) @@ -1092,6 +1092,21 @@ Weak reference list head, for weak references to this type object. Not inherited. Internal use only. + +.. cmember:: footprintfunc PyTypeObject.tp_footprint + + An optional function pointer that returns the memory footprint of the + object. This function should be used if the memory usage of the object + cannot be determined with tp_basicsize plus, if applicable, + tp_itemsize * length of the object. If the field is not set, the memory + footprint is computed as described. Note that this formula does not + include any referenced objects, e.g. attributes. + + The value -1 should not be returned as a normal return value; when an + error occurs during the computation of the hash value, the function + should set an exception and return -1. + + The remaining fields are only defined if the feature test macro :const:`COUNT_ALLOCS` is defined, and are for internal use only. They are documented here for completeness. None of these fields are inherited by Index: Doc/library/functions.rst =================================================================== --- Doc/library/functions.rst (revision 63363) +++ Doc/library/functions.rst (working copy) @@ -472,6 +472,16 @@ The float type is described in :ref:`typesnumeric`. + +.. function:: footprint(x) + + Return the size of an object in bytes. The object can be any type of + object. All built-in objects will return correct results, but this + must not hold true for third-party extensions. + + .. versionadded:: 2.6 + + .. function:: frozenset([iterable]) :noindex: Index: Doc/includes/typestruct.h =================================================================== --- Doc/includes/typestruct.h (revision 63363) +++ Doc/includes/typestruct.h (working copy) @@ -72,5 +72,12 @@ PyObject *tp_cache; PyObject *tp_subclasses; PyObject *tp_weaklist; + destructor tp_del; + /* Type attribute cache version tag. Added in version 2.6 */ + unsigned int tp_version_tag; + + /* Memory usage. Added in version 2.6 */ + footprintfunc tp_footprint; + } PyTypeObject; Index: Lib/test/test_builtin.py =================================================================== --- Lib/test/test_builtin.py (revision 63363) +++ Lib/test/test_builtin.py (working copy) @@ -1467,6 +1467,135 @@ self.assertEqual(bin(-(2**65)), '-0b1' + '0' * 65) self.assertEqual(bin(-(2**65-1)), '-0b' + '1' * 65) +class FootprintTest(unittest.TestCase): + + + def setUp(self): + import struct + self.i = len(struct.pack('i', 0)) + self.l = len(struct.pack('l', 0)) + self.p = len(struct.pack('P', 0)) + self.headersize = self.l + self.p + if hasattr(sys, "gettotalrefcount"): + self.headersize += 2 * self.p + self.f = open(TESTFN, 'wb') + + def tearDown(self): + import os + if self.f: + self.f.close() + os.remove(TESTFN) + + def check_footprint(self, o, size, ): + size += self.headersize + msg = 'wrong size for ' + str(type(o)) + ': '+\ + str(footprint(o)) + ' != ' + str(size) + self.assertEqual(footprint(o), size, msg) + + def align(self, value): + mod = value % self.p + if mod != 0: + return value - mod + self.p + else: + return value + + def test_align(self): + self.assertTrue( (self.align(0) % self.p) == 0 ) + self.assertTrue( (self.align(1) % self.p) == 0 ) + self.assertTrue( (self.align(3) % self.p) == 0 ) + self.assertTrue( (self.align(4) % self.p) == 0 ) + self.assertTrue( (self.align(7) % self.p) == 0 ) + self.assertTrue( (self.align(8) % self.p) == 0 ) + self.assertTrue( (self.align(9) % self.p) == 0 ) + + def test_standardobjects(self): + import inspect + i = self.i + l = self.l + p = self.p + # bool + self.check_footprint(True, self.l) + # buffer + self.check_footprint(buffer(''), 2*p + 2*l + self.align(i) +l) + # bytearray + self.check_footprint(bytes(), self.align(i) + l + p) + # cell + def get_cell(): + x = 42 + def inner(): + return x + return inner + self.check_footprint(get_cell().func_closure[0], p) + # class + class clazz(): + def method(): + pass + self.check_footprint(clazz, 6*p) + # instance + self.check_footprint(clazz(), 3*p) + # method + self.check_footprint(clazz().method, 4*p) + # code + self.check_footprint(get_cell().func_code, self.align(4*i) + 8*p +\ + self.align(i) + 2*p) + # complex + self.check_footprint(complex(0,1), 2*8) + # enumerate + self.check_footprint(enumerate([]), l + 3*p) + # reverse + self.check_footprint(reversed(''), l + p ) + # file + self.check_footprint(self.f, 4*p + self.align(2*i) + 4*p +\ + self.align(3*i) + 2*p + self.align(i)) + # float + self.check_footprint(float(0), 8) + # function + def func(): pass + self.check_footprint(func, 9 * l) + class c(): + @staticmethod + def foo(): + pass + @classmethod + def bar(cls): + pass + # staticmethod + self.check_footprint(foo, l) + # classmethod + self.check_footprint(bar, l) + # generator + def get_gen(): yield 1 + self.check_footprint(get_gen(), p + self.align(i) + 2*p) + # integer + self.check_footprint(1, l) + # builtin_function_or_method + self.check_footprint(abs, 3*p) + # module + self.check_footprint(unittest, p) + # xange + self.check_footprint(xrange(1), 3*p) + # slice + self.check_footprint(slice(0), 3*p) + + def test_variable_size(self): + i = self.i + l = self.l + p = self.p + self.headersize += l + # list + self.check_footprint([], p + l) + self.check_footprint([1, 2, 3], p + l) + # string + self.check_footprint('', l + self.align(i + 1)) + self.check_footprint('abc', l + self.align(i + 1) + 3) + + def test_special_types(self): + i = self.i + l = self.l + p = self.p + # dict + self.check_footprint({}, 3*l + 3*p + 8*(l + 2*p)) + class TestSorted(unittest.TestCase): def test_basic(self): @@ -1507,7 +1636,7 @@ self.assertRaises(TypeError, sorted, data, None, lambda x,y: 0) def test_main(verbose=None): - test_classes = (BuiltinTest, TestSorted) + test_classes = (BuiltinTest, TestSorted, FootprintTest) run_unittest(*test_classes)