Commit 7bd85c18 authored by David Glick's avatar David Glick

Intern keys in `__setstate__`

Refs zopefoundation/persistent#15
parent 7a57ed9b
...@@ -4,6 +4,9 @@ ...@@ -4,6 +4,9 @@
4.0.9 (unreleased) 4.0.9 (unreleased)
------------------ ------------------
- Intern keys of object state in `__setstate__` to reduce memory usage
when unpickling multiple objects with the same attributes.
- Add support for PyPy3. - Add support for PyPy3.
- 100% branch coverage. - 100% branch coverage.
......
...@@ -23,6 +23,8 @@ ...@@ -23,6 +23,8 @@
#ifdef PY3K #ifdef PY3K
#define INTERN PyUnicode_InternFromString #define INTERN PyUnicode_InternFromString
#define INTERN_INPLACE PyUnicode_InternInPlace
#define NATIVE_CHECK_EXACT PyUnicode_CheckExact
#define NATIVE_FROM_STRING_AND_SIZE PyUnicode_FromStringAndSize #define NATIVE_FROM_STRING_AND_SIZE PyUnicode_FromStringAndSize
#define Py_TPFLAGS_HAVE_RICHCOMPARE 0 #define Py_TPFLAGS_HAVE_RICHCOMPARE 0
...@@ -34,6 +36,8 @@ ...@@ -34,6 +36,8 @@
#else #else
#define INTERN PyString_InternFromString #define INTERN PyString_InternFromString
#define INTERN_INPLACE PyString_InternInPlace
#define NATIVE_CHECK_EXACT PyString_CheckExact
#define NATIVE_FROM_STRING_AND_SIZE PyString_FromStringAndSize #define NATIVE_FROM_STRING_AND_SIZE PyString_FromStringAndSize
#define INT_FROM_LONG(x) PyInt_FromLong(x) #define INT_FROM_LONG(x) PyInt_FromLong(x)
......
...@@ -18,6 +18,7 @@ if sys.version_info[0] > 2: #pragma NO COVER ...@@ -18,6 +18,7 @@ if sys.version_info[0] > 2: #pragma NO COVER
import copyreg as copy_reg import copyreg as copy_reg
from collections import UserDict as IterableUserDict from collections import UserDict as IterableUserDict
from collections import UserList from collections import UserList
from sys import intern
def _u(s): def _u(s):
return s return s
...@@ -52,3 +53,5 @@ else: #pragma NO COVER ...@@ -52,3 +53,5 @@ else: #pragma NO COVER
PYTHON3 = False PYTHON3 = False
PYTHON2 = True PYTHON2 = True
intern = intern
...@@ -534,6 +534,8 @@ pickle___setstate__(PyObject *self, PyObject *state) ...@@ -534,6 +534,8 @@ pickle___setstate__(PyObject *self, PyObject *state)
if (state != Py_None) if (state != Py_None)
{ {
PyObject **dict; PyObject **dict;
PyObject *d_key, *d_value;
Py_ssize_t i;
dict = _PyObject_GetDictPtr(self); dict = _PyObject_GetDictPtr(self);
...@@ -552,8 +554,20 @@ pickle___setstate__(PyObject *self, PyObject *state) ...@@ -552,8 +554,20 @@ pickle___setstate__(PyObject *self, PyObject *state)
} }
PyDict_Clear(*dict); PyDict_Clear(*dict);
if (PyDict_Update(*dict, state) < 0)
return NULL; i = 0;
while (PyDict_Next(state, &i, &d_key, &d_value)) {
/* normally the keys for instance attributes are
interned. we should try to do that here. */
Py_INCREF(d_key);
if (NATIVE_CHECK_EXACT(d_key))
INTERN_INPLACE(&d_key);
if (PyObject_SetItem(*dict, d_key, d_value) < 0) {
Py_DECREF(d_key);
return NULL;
}
Py_DECREF(d_key);
}
} }
if (slots && pickle_setattrs_from_dict(self, slots) < 0) if (slots && pickle_setattrs_from_dict(self, slots) < 0)
......
...@@ -25,6 +25,7 @@ from persistent.interfaces import SERIAL_TYPE ...@@ -25,6 +25,7 @@ from persistent.interfaces import SERIAL_TYPE
from persistent.timestamp import TimeStamp from persistent.timestamp import TimeStamp
from persistent.timestamp import _ZERO from persistent.timestamp import _ZERO
from persistent._compat import copy_reg from persistent._compat import copy_reg
from persistent._compat import intern
_INITIAL_SERIAL = _ZERO _INITIAL_SERIAL = _ZERO
...@@ -313,7 +314,8 @@ class Persistent(object): ...@@ -313,7 +314,8 @@ class Persistent(object):
if idict is None: if idict is None:
raise TypeError('No instance dict') raise TypeError('No instance dict')
idict.clear() idict.clear()
idict.update(inst_dict) for k, v in inst_dict.items():
idict[intern(k)] = v
slotnames = self._slotnames() slotnames = self._slotnames()
if slotnames: if slotnames:
for k, v in slots.items(): for k, v in slots.items():
......
...@@ -863,6 +863,20 @@ class _Persistent_Base(object): ...@@ -863,6 +863,20 @@ class _Persistent_Base(object):
self.assertEqual(inst.baz, 'bam') self.assertEqual(inst.baz, 'bam')
self.assertEqual(inst.qux, 'spam') self.assertEqual(inst.qux, 'spam')
def test___setstate___interns_dict_keys(self):
class Derived(self._getTargetClass()):
pass
inst1 = Derived()
inst2 = Derived()
key1 = 'key'
key2 = 'ke'; key2 += 'y' # construct in a way that won't intern the literal
self.assertFalse(key1 is key2)
inst1.__setstate__({key1: 1})
inst2.__setstate__({key2: 2})
key1 = list(inst1.__dict__.keys())[0]
key2 = list(inst2.__dict__.keys())[0]
self.assertTrue(key1 is key2)
def test___reduce__(self): def test___reduce__(self):
from persistent._compat import copy_reg from persistent._compat import copy_reg
inst = self._makeOne() inst = self._makeOne()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment