diff -r 4daffae06f64 -r 54b4712f2933 Include/dictobject.h --- a/Include/dictobject.h Fri Jan 08 01:03:03 2016 -0800 +++ b/Include/dictobject.h Sat Jan 09 03:16:58 2016 +0100 @@ -23,6 +23,7 @@ typedef struct _dictkeysobject PyDictKey typedef struct { PyObject_HEAD Py_ssize_t ma_used; + size_t ma_version; PyDictKeysObject *ma_keys; PyObject **ma_values; } PyDictObject; diff -r 4daffae06f64 -r 54b4712f2933 Objects/dictobject.c --- a/Objects/dictobject.c Fri Jan 08 01:03:03 2016 -0800 +++ b/Objects/dictobject.c Sat Jan 09 03:16:58 2016 +0100 @@ -69,6 +69,7 @@ to the combined-table form. #include "Python.h" #include "dict-common.h" #include "stringlib/eq.h" +#include "structmember.h" /*[clinic input] class dict "PyDictObject *" "&PyDict_Type" @@ -188,6 +189,11 @@ static PyObject _dummy_struct; #define dummy (&_dummy_struct) +/* FIXME: handle integer overflow: never use value 0. + The version 0 is reserved for "missing key". */ +#define DICT_INC_VERSION(mp) \ + do { ((PyDictObject *)(mp))->ma_version++; } while (0) + #ifdef Py_REF_DEBUG PyObject * _PyDict_Dummy(void) @@ -383,6 +389,7 @@ new_dict(PyDictKeysObject *keys, PyObjec mp->ma_keys = keys; mp->ma_values = values; mp->ma_used = 0; + mp->ma_version = 0; return (PyObject *)mp; } @@ -802,13 +809,19 @@ insertdict(PyDictObject *mp, PyObject *k assert(PyUnicode_CheckExact(key) || mp->ma_keys->dk_lookup == lookdict); Py_INCREF(value); MAINTAIN_TRACKING(mp, key, value); + old_value = *value_addr; if (old_value != NULL) { assert(ep->me_key != NULL && ep->me_key != dummy); *value_addr = value; + if (value != old_value) { + DICT_INC_VERSION(mp); + } Py_DECREF(old_value); /* which **CAN** re-enter (see issue #22653) */ } else { + DICT_INC_VERSION(mp); + if (ep->me_key == NULL) { Py_INCREF(key); if (mp->ma_keys->dk_usable <= 0) { @@ -1280,6 +1293,8 @@ PyDict_DelItem(PyObject *op, PyObject *k _PyErr_SetKeyError(key); return -1; } + + DICT_INC_VERSION(mp); old_value = *value_addr; *value_addr = NULL; mp->ma_used--; @@ -1350,6 +1365,7 @@ PyDict_Clear(PyObject *op) mp->ma_keys = Py_EMPTY_KEYS; mp->ma_values = empty_values; mp->ma_used = 0; + DICT_INC_VERSION(mp); /* ...then clear the keys and values */ if (oldvalues != NULL) { n = DK_SIZE(oldkeys); @@ -1483,8 +1499,11 @@ PyObject * _PyErr_SetKeyError(key); return NULL; } + *value_addr = NULL; + DICT_INC_VERSION(mp); mp->ma_used--; + if (!_PyDict_HasSplitTable(mp)) { ENSURE_ALLOWS_DELETIONS(mp); old_key = ep->me_key; @@ -2417,6 +2436,7 @@ PyDict_SetDefault(PyObject *d, PyObject val = defaultobj; mp->ma_keys->dk_usable--; mp->ma_used++; + DICT_INC_VERSION(mp); } return val; } @@ -2515,6 +2535,7 @@ dict_popitem(PyDictObject *mp) Py_INCREF(dummy); ep->me_key = dummy; ep->me_value = NULL; + DICT_INC_VERSION(mp); mp->ma_used--; assert(mp->ma_keys->dk_entries[0].me_value == NULL); mp->ma_keys->dk_entries[0].me_hash = i + 1; /* next place to start */ @@ -2720,6 +2741,7 @@ dict_new(PyTypeObject *type, PyObject *a _PyObject_GC_UNTRACK(d); d->ma_used = 0; + d->ma_version = 0; d->ma_keys = new_keys_object(PyDict_MINSIZE_COMBINED); if (d->ma_keys == NULL) { Py_DECREF(self); @@ -2740,6 +2762,12 @@ dict_iter(PyDictObject *dict) return dictiter_new(dict, &PyDictIterKey_Type); } +static PyMemberDef mapp_memberlist[] = { + {"__version__", T_ULONG, offsetof(PyDictObject, ma_version), READONLY, + "dictionary object"}, + {NULL} /* Sentinel */ +}; + PyDoc_STRVAR(dictionary_doc, "dict() -> new empty dictionary\n" "dict(mapping) -> new dictionary initialized from a mapping object's\n" @@ -2781,7 +2809,7 @@ PyTypeObject PyDict_Type = { (getiterfunc)dict_iter, /* tp_iter */ 0, /* tp_iternext */ mapp_methods, /* tp_methods */ - 0, /* tp_members */ + mapp_memberlist, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */