Updated patch and more justifications. New patch: - dict doesn't inherit from frozendict anymore - frozendict is a subclass of collections.abc.Mutable - more tests
> * frozendict.__hash__ computes hash(frozenset(self.items())) and > caches the result is its private hash attribute hash(frozenset(self.items())) is preferred over hash(sorted(self.items())) because keys and values may be unorderable. frozenset() is faster than sorted(): O(n) vs O(n*log(n)). frozendict hash doesn't care of the item order creation: >>> a=frozendict.fromkeys('ai') >>> a frozendict({'a': None, 'i': None}) >>> b=frozendict.fromkeys('ia') >>> b frozendict({'i': None, 'a': None}) >>> hash(a) == hash(b) True >>> a == b True >>> tuple(a.items()) == tuple(b.items()) False frozendict supports unorderable keys and values: >>> hash(frozendict({b'abc': 1, 'abc': 2})) 935669091 >>> hash(frozendict({1: b'abc', 2: 'abc'})) 1319859033 > * Add a frozendict abstract base class to collections? I realized that Mapping already exists and so the following patch is enough: +Mapping.register(frozendict) > See also the PEP 351. I read the PEP and the email explaining why it was rejected. Just to be clear: the PEP 351 tries to freeze an object, try to convert a mutable or immutable object to an immutable object. Whereas my frozendict proposition doesn't convert anything: it just raises a TypeError if you use a mutable key or value. For example, frozendict({'list': ['a', 'b', 'c']}) doesn't create frozendict({'list': ('a', 'b', 'c')}) but raises a TypeError. Victor
diff --git a/Include/dictobject.h b/Include/dictobject.h --- a/Include/dictobject.h +++ b/Include/dictobject.h @@ -84,10 +84,14 @@ struct _dictobject { PyDictEntry *ma_table; PyDictEntry *(*ma_lookup)(PyDictObject *mp, PyObject *key, Py_hash_t hash); PyDictEntry ma_smalltable[PyDict_MINSIZE]; + + /* only used by frozendict */ + Py_hash_t hash; }; #endif /* Py_LIMITED_API */ PyAPI_DATA(PyTypeObject) PyDict_Type; +PyAPI_DATA(PyTypeObject) PyFrozenDict_Type; PyAPI_DATA(PyTypeObject) PyDictIterKey_Type; PyAPI_DATA(PyTypeObject) PyDictIterValue_Type; PyAPI_DATA(PyTypeObject) PyDictIterItem_Type; @@ -97,7 +101,12 @@ PyAPI_DATA(PyTypeObject) PyDictValues_Ty #define PyDict_Check(op) \ PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_DICT_SUBCLASS) +#define PyFrozenDict_Check(op) \ + (PyDict_Check(op) || \ + Py_TYPE(op) == &PyFrozenDict_Type || \ + PyType_IsSubtype(Py_TYPE(op), &PyFrozenDict_Type)) #define PyDict_CheckExact(op) (Py_TYPE(op) == &PyDict_Type) +#define PyFrozenDict_CheckExact(op) (Py_TYPE(op) == &PyFrozenDict_Type) #define PyDictKeys_Check(op) (Py_TYPE(op) == &PyDictKeys_Type) #define PyDictItems_Check(op) (Py_TYPE(op) == &PyDictItems_Type) #define PyDictValues_Check(op) (Py_TYPE(op) == &PyDictValues_Type) diff --git a/Lib/collections/abc.py b/Lib/collections/abc.py --- a/Lib/collections/abc.py +++ b/Lib/collections/abc.py @@ -401,6 +401,7 @@ class Mapping(Sized, Iterable, Container def __ne__(self, other): return not (self == other) +Mapping.register(frozendict) class MappingView(Sized): diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -788,11 +788,66 @@ class Dict(dict): class SubclassMappingTests(mapping_tests.BasicTestMappingProtocol): type2test = Dict + +class FrozenDictTests(unittest.TestCase): + def test_constructor(self): + # unorderable keys and values + frozendict(x=b'abc', y='abc') + frozendict({b'abc': 1, 'abc': 2}) + + # hashable keys and values + class Hashable: + pass + for hashable in (5, 1.0, "abc", (1, 2), Hashable()): + frozendict(key=hashable) + frozendict(hashable=0) + + # not hashable keys or values + class NotHashable: + def __hash__(self): + raise TypeError("not hashable") + for not_hashable in ([], {}, NotHashable()): + self.assertRaises(TypeError, frozendict, key=not_hashable) + self.assertRaises(TypeError, frozendict, [(not_hashable, 0)]) + self.assertRaises(TypeError, frozendict, [(0, not_hashable)]) + + def test_copy(self): + self.assertIsInstance(frozendict().copy(), + frozendict) + self.assertEqual(frozendict(x=1, y=2).copy(), + frozendict(x=1, y=2)) + + def test_dir(self): + self.assertEqual( + set(dir(frozendict)) - set(dir(object)), + {'__contains__', '__getitem__', '__iter__', '__len__', + 'copy', 'fromkeys', 'get', 'items', 'keys', 'values'}) + + def test_fromkeys(self): + self.assertEqual(frozendict.fromkeys('ai'), + frozendict(a=None, i=None)) + + def test_hash(self): + self.assertEqual(hash(frozendict()), + hash(frozenset())) + self.assertEqual(hash(frozendict({1: 2})), + hash(frozenset({(1, 2)}))) + a = frozendict.fromkeys('ai') + b = frozendict.fromkeys('ia') + self.assertEqual(hash(a), hash(b)) + self.assertNotEqual(tuple(a.items()), tuple(b.items())) + + def test_repr(self): + self.assertEqual(repr(frozendict()), "frozendict({})") + self.assertEqual(repr(frozendict(x=1)), "frozendict({'x': 1})") + + def test_main(): support.run_unittest( DictTest, GeneralMappingTests, SubclassMappingTests, + FrozenDictTests, ) if __name__ == "__main__": diff --git a/Objects/dictobject.c b/Objects/dictobject.c --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -10,7 +10,6 @@ #include "Python.h" #include "stringlib/eq.h" - /* Set a key error with the specified argument, wrapping it in a * tuple automatically so that tuple keys are not unpacked as the * exception arguments. */ @@ -25,6 +24,25 @@ set_key_error(PyObject *arg) Py_DECREF(tup); } +static int +check_immutable(PyObject *obj) +{ + /* fast-path for common known immutable types */ + if (PyLong_Check(obj) || PyFloat_Check(obj) || PyUnicode_Check(obj) + || PyTuple_Check(obj)) + return 0; + if (PyObject_Hash(obj) != -1) + return 0; + else + return -1; +} + +static int +dict_merge(PyObject *a, PyObject *b, int override, int is_frozen); +static int +dict_mergefromseq2(PyObject *d, PyObject *seq2, int override, int is_frozen); +static PyObject *frozendict_new(void); + /* Define this out if you don't want conversion statistics on exit. */ #undef SHOW_CONVERSION_COUNTS @@ -461,7 +479,7 @@ _PyDict_HasOnlyStringKeys(PyObject *dict { Py_ssize_t pos = 0; PyObject *key, *value; - assert(PyDict_Check(dict)); + assert(PyFrozenDict_Check(dict)); /* Shortcut */ if (((PyDictObject *)dict)->ma_lookup == lookdict_unicode) return 1; @@ -523,10 +541,13 @@ Used by insertdict. */ static int insertdict_by_entry(register PyDictObject *mp, PyObject *key, Py_hash_t hash, - PyDictEntry *ep, PyObject *value) + PyDictEntry *ep, PyObject *value, int is_frozen) { PyObject *old_value; + if (is_frozen && check_immutable(value) == -1) + return -1; + MAINTAIN_TRACKING(mp, key, value); if (ep->me_value != NULL) { old_value = ep->me_value; @@ -557,10 +578,14 @@ Eats a reference to key and one to value Returns -1 if an error occurred, or 0 on success. */ static int -insertdict(register PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject *value) +insertdict(register PyDictObject *mp, PyObject *key, + Py_hash_t hash, PyObject *value, int is_frozen) { register PyDictEntry *ep; + if (is_frozen && check_immutable(value) == -1) + return -1; + assert(mp->ma_lookup != NULL); ep = mp->ma_lookup(mp, key, hash); if (ep == NULL) { @@ -568,7 +593,7 @@ insertdict(register PyDictObject *mp, Py Py_DECREF(value); return -1; } - return insertdict_by_entry(mp, key, hash, ep, value); + return insertdict_by_entry(mp, key, hash, ep, value, is_frozen); } /* @@ -725,7 +750,7 @@ PyDict_GetItem(PyObject *op, PyObject *k PyDictObject *mp = (PyDictObject *)op; PyDictEntry *ep; PyThreadState *tstate; - if (!PyDict_Check(op)) + if (!PyFrozenDict_Check(op)) return NULL; if (!PyUnicode_CheckExact(key) || (hash = ((PyASCIIObject *) key)->hash) == -1) @@ -775,7 +800,7 @@ PyDict_GetItemWithError(PyObject *op, Py PyDictObject*mp = (PyDictObject *)op; PyDictEntry *ep; - if (!PyDict_Check(op)) { + if (!PyFrozenDict_Check(op)) { PyErr_BadInternalCall(); return NULL; } @@ -796,7 +821,8 @@ PyDict_GetItemWithError(PyObject *op, Py static int dict_set_item_by_hash_or_entry(register PyObject *op, PyObject *key, - Py_hash_t hash, PyDictEntry *ep, PyObject *value) + Py_hash_t hash, PyDictEntry *ep, PyObject *value, + int is_frozen) { register PyDictObject *mp; register Py_ssize_t n_used; @@ -807,11 +833,11 @@ dict_set_item_by_hash_or_entry(register Py_INCREF(value); Py_INCREF(key); if (ep == NULL) { - if (insertdict(mp, key, hash, value) != 0) + if (insertdict(mp, key, hash, value, is_frozen) != 0) return -1; } else { - if (insertdict_by_entry(mp, key, hash, ep, value) != 0) + if (insertdict_by_entry(mp, key, hash, ep, value, is_frozen) != 0) return -1; } /* If we added a key, we can safely resize. Otherwise just return! @@ -840,6 +866,27 @@ dict_set_item_by_hash_or_entry(register * remove them. */ int +basedict_setitem(PyObject *op, PyObject *key, PyObject *value, int is_frozen) +{ + register Py_hash_t hash; + + assert(PyFrozenDict_Check(op)); + assert(key); + assert(value); + if (PyUnicode_CheckExact(key)) { + hash = ((PyASCIIObject *) key)->hash; + if (hash == -1) + hash = PyObject_Hash(key); + } + else { + hash = PyObject_Hash(key); + if (hash == -1) + return -1; + } + return dict_set_item_by_hash_or_entry(op, key, hash, NULL, value, is_frozen); +} + +int PyDict_SetItem(register PyObject *op, PyObject *key, PyObject *value) { register Py_hash_t hash; @@ -860,7 +907,7 @@ PyDict_SetItem(register PyObject *op, Py if (hash == -1) return -1; } - return dict_set_item_by_hash_or_entry(op, key, hash, NULL, value); + return dict_set_item_by_hash_or_entry(op, key, hash, NULL, value, 0); } int @@ -913,9 +960,10 @@ PyDict_Clear(PyObject *op) Py_ssize_t i, n; #endif - if (!PyDict_Check(op)) + if (!PyFrozenDict_Check(op)) return; mp = (PyDictObject *)op; + #ifdef Py_DEBUG n = mp->ma_mask + 1; i = 0; @@ -992,7 +1040,7 @@ PyDict_Next(PyObject *op, Py_ssize_t *pp register Py_ssize_t mask; register PyDictEntry *ep; - if (!PyDict_Check(op)) + if (!PyFrozenDict_Check(op)) return 0; i = *ppos; if (i < 0) @@ -1019,7 +1067,7 @@ _PyDict_Next(PyObject *op, Py_ssize_t *p register Py_ssize_t mask; register PyDictEntry *ep; - if (!PyDict_Check(op)) + if (!PyFrozenDict_Check(op)) return 0; i = *ppos; if (i < 0) @@ -1143,6 +1191,18 @@ Done: return result; } +static PyObject * +frozendict_repr(PyDictObject *mp) +{ + PyObject *repr, *result; + repr = dict_repr(mp); + if (repr == NULL) + return NULL; + result = PyUnicode_FromFormat("frozendict(%S)", repr); + Py_DECREF(repr); + return result; +} + static Py_ssize_t dict_length(PyDictObject *mp) { @@ -1198,6 +1258,11 @@ dict_ass_sub(PyDictObject *mp, PyObject return PyDict_SetItem((PyObject *)mp, v, w); } +static PyMappingMethods frozendict_as_mapping = { + (lenfunc)dict_length, /*mp_length*/ + (binaryfunc)dict_subscript, /*mp_subscript*/ +}; + static PyMappingMethods dict_as_mapping = { (lenfunc)dict_length, /*mp_length*/ (binaryfunc)dict_subscript, /*mp_subscript*/ @@ -1324,7 +1389,7 @@ dict_items(register PyDictObject *mp) } static PyObject * -dict_fromkeys(PyObject *cls, PyObject *args) +basedict_fromkeys(PyObject *cls, PyObject *args, int is_frozen) { PyObject *seq; PyObject *value = Py_None; @@ -1355,7 +1420,7 @@ dict_fromkeys(PyObject *cls, PyObject *a while (_PyDict_Next(seq, &pos, &key, &oldvalue, &hash)) { Py_INCREF(key); Py_INCREF(value); - if (insertdict(mp, key, hash, value)) { + if (insertdict(mp, key, hash, value, is_frozen)) { Py_DECREF(d); return NULL; } @@ -1377,7 +1442,7 @@ dict_fromkeys(PyObject *cls, PyObject *a while (_PySet_NextEntry(seq, &pos, &key, &hash)) { Py_INCREF(key); Py_INCREF(value); - if (insertdict(mp, key, hash, value)) { + if (insertdict(mp, key, hash, value, is_frozen)) { Py_DECREF(d); return NULL; } @@ -1391,9 +1456,9 @@ dict_fromkeys(PyObject *cls, PyObject *a return NULL; } - if (PyDict_CheckExact(d)) { + if (PyFrozenDict_CheckExact(d) || PyDict_CheckExact(d)) { while ((key = PyIter_Next(it)) != NULL) { - status = PyDict_SetItem(d, key, value); + status = basedict_setitem(d, key, value, is_frozen); Py_DECREF(key); if (status < 0) goto Fail; @@ -1418,8 +1483,21 @@ Fail: return NULL; } +static PyObject * +frozendict_fromkeys(PyObject *cls, PyObject *args) +{ + return basedict_fromkeys(cls, args, 1); +} + +static PyObject * +dict_fromkeys(PyObject *cls, PyObject *args) +{ + return basedict_fromkeys(cls, args, 0); +} + static int -dict_update_common(PyObject *self, PyObject *args, PyObject *kwds, char *methname) +dict_update_common(PyObject *self, PyObject *args, PyObject *kwds, + char *methname, int is_frozen) { PyObject *arg = NULL; int result = 0; @@ -1430,13 +1508,13 @@ dict_update_common(PyObject *self, PyObj else if (arg != NULL) { _Py_IDENTIFIER(keys); if (_PyObject_HasAttrId(arg, &PyId_keys)) - result = PyDict_Merge(self, arg, 1); + result = dict_merge(self, arg, 1, is_frozen); else - result = PyDict_MergeFromSeq2(self, arg, 1); + result = dict_mergefromseq2(self, arg, 1, is_frozen); } if (result == 0 && kwds != NULL) { if (PyArg_ValidateKeywordArguments(kwds)) - result = PyDict_Merge(self, kwds, 1); + result = dict_merge(self, kwds, 1, is_frozen); else result = -1; } @@ -1446,7 +1524,7 @@ dict_update_common(PyObject *self, PyObj static PyObject * dict_update(PyObject *self, PyObject *args, PyObject *kwds) { - if (dict_update_common(self, args, kwds, "update") != -1) + if (dict_update_common(self, args, kwds, "update", 0) != -1) Py_RETURN_NONE; return NULL; } @@ -1461,8 +1539,8 @@ dict_update(PyObject *self, PyObject *ar producing iterable objects of length 2. */ -int -PyDict_MergeFromSeq2(PyObject *d, PyObject *seq2, int override) +static int +dict_mergefromseq2(PyObject *d, PyObject *seq2, int override, int is_frozen) { PyObject *it; /* iter(seq2) */ Py_ssize_t i; /* index into seq2 of current element */ @@ -1470,7 +1548,7 @@ PyDict_MergeFromSeq2(PyObject *d, PyObje PyObject *fast; /* item as a 2-tuple or 2-list */ assert(d != NULL); - assert(PyDict_Check(d)); + assert(PyFrozenDict_Check(d)); assert(seq2 != NULL); it = PyObject_GetIter(seq2); @@ -1511,8 +1589,10 @@ PyDict_MergeFromSeq2(PyObject *d, PyObje /* Update/merge with this (key, value) pair. */ key = PySequence_Fast_GET_ITEM(fast, 0); value = PySequence_Fast_GET_ITEM(fast, 1); + if (is_frozen && check_immutable(value) == -1) + goto Fail; if (override || PyDict_GetItem(d, key) == NULL) { - int status = PyDict_SetItem(d, key, value); + int status = basedict_setitem(d, key, value, is_frozen); if (status < 0) goto Fail; } @@ -1532,13 +1612,19 @@ Return: } int +PyDict_MergeFromSeq2(PyObject *d, PyObject *seq2, int override) +{ + return dict_mergefromseq2(d, seq2, override, 0); +} + +int PyDict_Update(PyObject *a, PyObject *b) { - return PyDict_Merge(a, b, 1); + return dict_merge(a, b, 1, 0); } -int -PyDict_Merge(PyObject *a, PyObject *b, int override) +static int +dict_merge(PyObject *a, PyObject *b, int override, int is_frozen) { register PyDictObject *mp, *other; register Py_ssize_t i; @@ -1549,12 +1635,13 @@ PyDict_Merge(PyObject *a, PyObject *b, i * things quite efficiently. For the latter, we only require that * PyMapping_Keys() and PyObject_GetItem() be supported. */ - if (a == NULL || !PyDict_Check(a) || b == NULL) { + if (a == NULL || !PyFrozenDict_Check(a) || b == NULL) { PyErr_BadInternalCall(); return -1; } mp = (PyDictObject*)a; - if (PyDict_Check(b)) { + + if (PyFrozenDict_Check(b)) { other = (PyDictObject*)b; if (other == mp || other->ma_used == 0) /* a.update(a) or a.update({}); nothing to do */ @@ -1575,16 +1662,17 @@ PyDict_Merge(PyObject *a, PyObject *b, i } for (i = 0; i <= other->ma_mask; i++) { entry = &other->ma_table[i]; - if (entry->me_value != NULL && - (override || - PyDict_GetItem(a, entry->me_key) == NULL)) { - Py_INCREF(entry->me_key); - Py_INCREF(entry->me_value); - if (insertdict(mp, entry->me_key, - entry->me_hash, - entry->me_value) != 0) - return -1; - } + if (entry->me_value == NULL) + continue; + if (!override && PyDict_GetItem(a, entry->me_key) != NULL) + continue; + Py_INCREF(entry->me_key); + Py_INCREF(entry->me_value); + if (insertdict(mp, entry->me_key, + entry->me_hash, + entry->me_value, + is_frozen) != 0) + return -1; } } else { @@ -1618,7 +1706,13 @@ PyDict_Merge(PyObject *a, PyObject *b, i Py_DECREF(key); return -1; } - status = PyDict_SetItem(a, key, value); + if (is_frozen && check_immutable(value) == -1) { + Py_DECREF(iter); + Py_DECREF(key); + Py_DECREF(value); + return -1; + } + status = basedict_setitem(a, key, value, is_frozen); Py_DECREF(key); Py_DECREF(value); if (status < 0) { @@ -1634,6 +1728,12 @@ PyDict_Merge(PyObject *a, PyObject *b, i return 0; } +int +PyDict_Merge(PyObject *a, PyObject *b, int override) +{ + return dict_merge(a, b, override, 0); +} + static PyObject * dict_copy(register PyDictObject *mp) { @@ -1652,16 +1752,33 @@ PyDict_Copy(PyObject *o) copy = PyDict_New(); if (copy == NULL) return NULL; - if (PyDict_Merge(copy, o, 1) == 0) + if (dict_merge(copy, o, 1, 0) == 0) return copy; Py_DECREF(copy); return NULL; } +static PyObject * +frozendict_copy(PyObject *o) +{ + PyObject *copy; + if (o == NULL || !PyFrozenDict_Check(o)) { + PyErr_BadInternalCall(); + return NULL; + } + copy = frozendict_new(); + if (copy == NULL) + return NULL; + if (dict_merge(copy, o, 1, 1) == 0) + return copy; + Py_DECREF(copy); + return NULL; +} + Py_ssize_t PyDict_Size(PyObject *mp) { - if (mp == NULL || !PyDict_Check(mp)) { + if (mp == NULL || !PyFrozenDict_Check(mp)) { PyErr_BadInternalCall(); return -1; } @@ -1671,7 +1788,7 @@ PyDict_Size(PyObject *mp) PyObject * PyDict_Keys(PyObject *mp) { - if (mp == NULL || !PyDict_Check(mp)) { + if (mp == NULL || !PyFrozenDict_Check(mp)) { PyErr_BadInternalCall(); return NULL; } @@ -1681,7 +1798,7 @@ PyDict_Keys(PyObject *mp) PyObject * PyDict_Values(PyObject *mp) { - if (mp == NULL || !PyDict_Check(mp)) { + if (mp == NULL || !PyFrozenDict_Check(mp)) { PyErr_BadInternalCall(); return NULL; } @@ -1691,7 +1808,7 @@ PyDict_Values(PyObject *mp) PyObject * PyDict_Items(PyObject *mp) { - if (mp == NULL || !PyDict_Check(mp)) { + if (mp == NULL || !PyFrozenDict_Check(mp)) { PyErr_BadInternalCall(); return NULL; } @@ -1746,7 +1863,7 @@ dict_richcompare(PyObject *v, PyObject * int cmp; PyObject *res; - if (!PyDict_Check(v) || !PyDict_Check(w)) { + if (!PyFrozenDict_Check(v) || !PyFrozenDict_Check(w)) { res = Py_NotImplemented; } else if (op == Py_EQ || op == Py_NE) { @@ -1832,7 +1949,7 @@ dict_setdefault(register PyDictObject *m val = ep->me_value; if (val == NULL) { if (dict_set_item_by_hash_or_entry((PyObject*)mp, key, hash, ep, - failobj) == 0) + failobj, 0) == 0) val = failobj; } Py_XINCREF(val); @@ -2034,7 +2151,29 @@ PyDoc_STRVAR(items__doc__, PyDoc_STRVAR(values__doc__, "D.values() -> an object providing a view on D's values"); -static PyMethodDef mapp_methods[] = { +static PyMethodDef frozendict_methods[] = { + {"__contains__",(PyCFunction)dict_contains, METH_O | METH_COEXIST, + contains__doc__}, + {"__getitem__", (PyCFunction)dict_subscript, METH_O | METH_COEXIST, + getitem__doc__}, + {"__sizeof__", (PyCFunction)dict_sizeof, METH_NOARGS, + sizeof__doc__}, + {"get", (PyCFunction)dict_get, METH_VARARGS, + get__doc__}, + {"keys", (PyCFunction)dictkeys_new, METH_NOARGS, + keys__doc__}, + {"items", (PyCFunction)dictitems_new, METH_NOARGS, + items__doc__}, + {"values", (PyCFunction)dictvalues_new, METH_NOARGS, + values__doc__}, + {"fromkeys", (PyCFunction)frozendict_fromkeys, METH_VARARGS | METH_CLASS, + fromkeys__doc__}, + {"copy", (PyCFunction)frozendict_copy, METH_NOARGS, + copy__doc__}, + {NULL, NULL} /* sentinel */ +}; + +static PyMethodDef dict_methods[] = { {"__contains__",(PyCFunction)dict_contains, METH_O | METH_COEXIST, contains__doc__}, {"__getitem__", (PyCFunction)dict_subscript, METH_O | METH_COEXIST, @@ -2138,10 +2277,37 @@ dict_new(PyTypeObject *type, PyObject *a return self; } +static PyObject * +frozendict_new(void) +{ + PyObject *self; + PyDictObject *d; + + self = dict_new(&PyFrozenDict_Type, NULL, NULL); + if (self == NULL) + return NULL; + d = (PyDictObject *)self; + d->hash = -1; + return self; +} + +static PyObject * +frozendict___new__(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyObject *self = frozendict_new(); + if (self == NULL) + return NULL; + if (dict_update_common(self, args, kwds, "frozendict", 1) == -1) { + Py_DECREF(self); + return NULL; + } + return self; +} + static int dict_init(PyObject *self, PyObject *args, PyObject *kwds) { - return dict_update_common(self, args, kwds, "dict"); + return dict_update_common(self, args, kwds, "dict", 0); } static PyObject * @@ -2150,6 +2316,86 @@ dict_iter(PyDictObject *dict) return dictiter_new(dict, &PyDictIterKey_Type); } +static Py_hash_t +frozendict_hash(PyObject *self) +{ + PyDictObject *frozendict = (PyDictObject *)self; + PyObject *items, *frozen_items; + Py_hash_t hash; + + if (frozendict->hash != -1) + return frozendict->hash; + + items = PyObject_CallMethod(self, "items", ""); + if (items == NULL) + return -1; + frozen_items = PyFrozenSet_New(items); + Py_DECREF(items); + if (frozen_items == NULL) + return -1; + hash = PyObject_Hash(frozen_items); + Py_DECREF(frozen_items); + if (hash == -1) + return -1; + + frozendict->hash = hash; + return hash; +} + +PyDoc_STRVAR(frozendict_doc, +"frozendict() -> new empty frozen dictionary\n" +"frozendict(mapping) -> new frozen dictionary initialized from a mapping object's\n" +" (key, value) pairs\n" +"frozendict(iterable) -> new frozen dictionary initialized as if via:\n" +" d = {}\n" +" for k, v in iterable:\n" +" d[k] = v\n" +"frozendict(**kwargs) -> new frozen dictionary initialized with the name=value\n" +" pairs in the keyword argument list. For example: frozendict(one=1, two=2)"); + +PyTypeObject PyFrozenDict_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + "frozendict", + sizeof(PyDictObject), + 0, + (destructor)dict_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + (reprfunc)frozendict_repr, /* tp_repr */ + 0, /* tp_as_number */ + &dict_as_sequence, /* tp_as_sequence */ + &frozendict_as_mapping, /* tp_as_mapping */ + frozendict_hash, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | + Py_TPFLAGS_BASETYPE, /* tp_flags */ + frozendict_doc, /* tp_doc */ + dict_traverse, /* tp_traverse */ + dict_tp_clear, /* tp_clear */ + dict_richcompare, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + (getiterfunc)dict_iter, /* tp_iter */ + 0, /* tp_iternext */ + frozendict_methods, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + PyType_GenericAlloc, /* tp_alloc */ + frozendict___new__, /* tp_new */ + PyObject_GC_Del, /* tp_free */ +}; + PyDoc_STRVAR(dictionary_doc, "dict() -> new empty dictionary\n" "dict(mapping) -> new dictionary initialized from a mapping object's\n" @@ -2190,7 +2436,7 @@ PyTypeObject PyDict_Type = { 0, /* tp_weaklistoffset */ (getiterfunc)dict_iter, /* tp_iter */ 0, /* tp_iternext */ - mapp_methods, /* tp_methods */ + dict_methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ @@ -2324,7 +2570,7 @@ static PyObject *dictiter_iternextkey(di if (d == NULL) return NULL; - assert (PyDict_Check(d)); + assert (PyFrozenDict_Check(d)); if (di->di_used != d->ma_used) { PyErr_SetString(PyExc_RuntimeError, @@ -2396,7 +2642,7 @@ static PyObject *dictiter_iternextvalue( if (d == NULL) return NULL; - assert (PyDict_Check(d)); + assert (PyFrozenDict_Check(d)); if (di->di_used != d->ma_used) { PyErr_SetString(PyExc_RuntimeError, @@ -2468,7 +2714,7 @@ static PyObject *dictiter_iternextitem(d if (d == NULL) return NULL; - assert (PyDict_Check(d)); + assert (PyFrozenDict_Check(d)); if (di->di_used != d->ma_used) { PyErr_SetString(PyExc_RuntimeError, @@ -2589,7 +2835,7 @@ dictview_new(PyObject *dict, PyTypeObjec PyErr_BadInternalCall(); return NULL; } - if (!PyDict_Check(dict)) { + if (!PyFrozenDict_Check(dict)) { /* XXX Get rid of this restriction later */ PyErr_Format(PyExc_TypeError, "%s() requires a dict argument, not '%s'", diff --git a/Objects/object.c b/Objects/object.c --- a/Objects/object.c +++ b/Objects/object.c @@ -1620,6 +1620,9 @@ _Py_ReadyTypes(void) if (PyType_Ready(&PyRange_Type) < 0) Py_FatalError("Can't initialize range type"); + if (PyType_Ready(&PyFrozenDict_Type) < 0) + Py_FatalError("Can't initialize frozendict type"); + if (PyType_Ready(&PyDict_Type) < 0) Py_FatalError("Can't initialize dict type"); diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -2396,6 +2396,7 @@ _PyBuiltin_Init(void) SETBUILTIN("enumerate", &PyEnum_Type); SETBUILTIN("filter", &PyFilter_Type); SETBUILTIN("float", &PyFloat_Type); + SETBUILTIN("frozendict", &PyFrozenDict_Type); SETBUILTIN("frozenset", &PyFrozenSet_Type); SETBUILTIN("property", &PyProperty_Type); SETBUILTIN("int", &PyLong_Type);
_______________________________________________ Python-Dev mailing list Python-Dev@python.org http://mail.python.org/mailman/listinfo/python-dev Unsubscribe: http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com