diff -r ba51573fcf90 Include/dictobject.h --- a/Include/dictobject.h Mon Feb 27 13:51:02 2012 +0100 +++ b/Include/dictobject.h Wed Feb 29 19:04:31 2012 +0100 @@ -85,9 +85,15 @@ struct _dictobject { PyDictEntry *(*ma_lookup)(PyDictObject *mp, PyObject *key, Py_hash_t hash); PyDictEntry ma_smalltable[PyDict_MINSIZE]; }; + +typedef struct { + PyDictObject _base; + Py_hash_t hash; +} PyFrozenDictObject; #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; @@ -98,6 +104,10 @@ PyAPI_DATA(PyTypeObject) PyDictValues_Ty #define PyDict_Check(op) \ PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_DICT_SUBCLASS) #define PyDict_CheckExact(op) (Py_TYPE(op) == &PyDict_Type) +#define PyFrozenDict_CheckExact(op) (Py_TYPE(op) == &PyFrozenDict_Type) +#define PyFrozenDict_Check(op) \ + (PyFrozenDict_CheckExact(op) || \ + PyType_IsSubtype(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 -r ba51573fcf90 Lib/collections/abc.py --- a/Lib/collections/abc.py Mon Feb 27 13:51:02 2012 +0100 +++ b/Lib/collections/abc.py Wed Feb 29 19:04:31 2012 +0100 @@ -401,6 +401,7 @@ class Mapping(Sized, Iterable, Container def __ne__(self, other): return not (self == other) +Mapping.register(frozendict) class MappingView(Sized): diff -r ba51573fcf90 Lib/doctest.py --- a/Lib/doctest.py Mon Feb 27 13:51:02 2012 +0100 +++ b/Lib/doctest.py Wed Feb 29 19:04:31 2012 +0100 @@ -1438,7 +1438,8 @@ class DocTestRunner: if clear_globs: test.globs.clear() import builtins - builtins._ = None + if hasattr(builtins, '_'): + builtins._ = None #///////////////////////////////////////////////////////////////// # Summarization diff -r ba51573fcf90 Lib/site.py --- a/Lib/site.py Mon Feb 27 13:51:02 2012 +0100 +++ b/Lib/site.py Wed Feb 29 19:04:31 2012 +0100 @@ -359,8 +359,8 @@ def setquit(): except: pass raise SystemExit(code) - builtins.quit = Quitter('quit') - builtins.exit = Quitter('exit') + #builtins.quit = Quitter('quit') + #builtins.exit = Quitter('exit') class _Printer(object): @@ -523,8 +523,8 @@ def main(): if sys.platform == 'os2emx': setBEGINLIBPATH() setquit() - setcopyright() - sethelper() + #setcopyright() + #sethelper() aliasmbcs() execsitecustomize() if ENABLE_USER_SITE: diff -r ba51573fcf90 Lib/test/mapping_tests.py --- a/Lib/test/mapping_tests.py Mon Feb 27 13:51:02 2012 +0100 +++ b/Lib/test/mapping_tests.py Wed Feb 29 19:04:31 2012 +0100 @@ -21,10 +21,7 @@ class BasicTestMappingProtocol(unittest. def _full_mapping(self, data): """Return a mapping object with the value contained in data dictionary""" - x = self._empty_mapping() - for key, value in data.items(): - x[key] = value - return x + return self.type2test(**data) def __init__(self, *args, **kw): unittest.TestCase.__init__(self, *args, **kw) @@ -87,48 +84,6 @@ class BasicTestMappingProtocol(unittest. self.assertEqual(d.get(knownkey, knownvalue), knownvalue) self.assertNotIn(knownkey, d) - def test_write(self): - # Test for write operations on mapping - p = self._empty_mapping() - #Indexing - for key, value in self.reference.items(): - p[key] = value - self.assertEqual(p[key], value) - for key in self.reference.keys(): - del p[key] - self.assertRaises(KeyError, lambda:p[key]) - p = self._empty_mapping() - #update - p.update(self.reference) - self.assertEqual(dict(p), self.reference) - items = list(p.items()) - p = self._empty_mapping() - p.update(items) - self.assertEqual(dict(p), self.reference) - d = self._full_mapping(self.reference) - #setdefault - key, value = next(iter(d.items())) - knownkey, knownvalue = next(iter(self.other.items())) - self.assertEqual(d.setdefault(key, knownvalue), value) - self.assertEqual(d[key], value) - self.assertEqual(d.setdefault(knownkey, knownvalue), knownvalue) - self.assertEqual(d[knownkey], knownvalue) - #pop - self.assertEqual(d.pop(knownkey), knownvalue) - self.assertNotIn(knownkey, d) - self.assertRaises(KeyError, d.pop, knownkey) - default = 909 - d[knownkey] = knownvalue - self.assertEqual(d.pop(knownkey, default), knownvalue) - self.assertNotIn(knownkey, d) - self.assertEqual(d.pop(knownkey, default), default) - #popitem - key, value = d.popitem() - self.assertNotIn(key, d) - self.assertEqual(value, self.reference[key]) - p=self._empty_mapping() - self.assertRaises(KeyError, p.popitem) - def test_constructor(self): self.assertEqual(self._empty_mapping(), self._empty_mapping()) @@ -169,6 +124,32 @@ class BasicTestMappingProtocol(unittest. self.assertRaises(TypeError, d.__getitem__) + # no test_fromkeys or test_copy as both os.environ and selves don't support it + + def test_get(self): + d = self._empty_mapping() + self.assertTrue(d.get(list(self.other.keys())[0]) is None) + self.assertEqual(d.get(list(self.other.keys())[0], 3), 3) + d = self.reference + self.assertTrue(d.get(list(self.other.keys())[0]) is None) + self.assertEqual(d.get(list(self.other.keys())[0], 3), 3) + self.assertEqual(d.get(list(self.inmapping.keys())[0]), + list(self.inmapping.values())[0]) + self.assertEqual(d.get(list(self.inmapping.keys())[0], 3), + list(self.inmapping.values())[0]) + self.assertRaises(TypeError, d.get) + self.assertRaises(TypeError, d.get, None, None, None) + + +class BasicTestMutableMappingProtocol(BasicTestMappingProtocol): + def _full_mapping(self, data): + """Return a mapping object with the value contained in data + dictionary""" + x = self._empty_mapping() + for key, value in data.items(): + x[key] = value + return x + def test_update(self): # mapping argument d = self._empty_mapping() @@ -264,21 +245,47 @@ class BasicTestMappingProtocol(unittest. self.assertRaises(ValueError, d.update, [(1, 2, 3)]) - # no test_fromkeys or test_copy as both os.environ and selves don't support it - - def test_get(self): - d = self._empty_mapping() - self.assertTrue(d.get(list(self.other.keys())[0]) is None) - self.assertEqual(d.get(list(self.other.keys())[0], 3), 3) - d = self.reference - self.assertTrue(d.get(list(self.other.keys())[0]) is None) - self.assertEqual(d.get(list(self.other.keys())[0], 3), 3) - self.assertEqual(d.get(list(self.inmapping.keys())[0]), - list(self.inmapping.values())[0]) - self.assertEqual(d.get(list(self.inmapping.keys())[0], 3), - list(self.inmapping.values())[0]) - self.assertRaises(TypeError, d.get) - self.assertRaises(TypeError, d.get, None, None, None) + def test_write(self): + # Test for write operations on mapping + p = self._empty_mapping() + #Indexing + for key, value in self.reference.items(): + p[key] = value + self.assertEqual(p[key], value) + for key in self.reference.keys(): + del p[key] + self.assertRaises(KeyError, lambda:p[key]) + p = self._empty_mapping() + #update + p.update(self.reference) + self.assertEqual(dict(p), self.reference) + items = list(p.items()) + p = self._empty_mapping() + p.update(items) + self.assertEqual(dict(p), self.reference) + d = self._full_mapping(self.reference) + #setdefault + key, value = next(iter(d.items())) + knownkey, knownvalue = next(iter(self.other.items())) + self.assertEqual(d.setdefault(key, knownvalue), value) + self.assertEqual(d[key], value) + self.assertEqual(d.setdefault(knownkey, knownvalue), knownvalue) + self.assertEqual(d[knownkey], knownvalue) + #pop + self.assertEqual(d.pop(knownkey), knownvalue) + self.assertNotIn(knownkey, d) + self.assertRaises(KeyError, d.pop, knownkey) + default = 909 + d[knownkey] = knownvalue + self.assertEqual(d.pop(knownkey, default), knownvalue) + self.assertNotIn(knownkey, d) + self.assertEqual(d.pop(knownkey, default), default) + #popitem + key, value = d.popitem() + self.assertNotIn(key, d) + self.assertEqual(value, self.reference[key]) + p=self._empty_mapping() + self.assertRaises(KeyError, p.popitem) def test_setdefault(self): d = self._empty_mapping() @@ -301,21 +308,21 @@ class BasicTestMappingProtocol(unittest. self.assertRaises(KeyError, d.pop, k) -class TestMappingProtocol(BasicTestMappingProtocol): +class TestMappingProtocol(BasicTestMutableMappingProtocol): def test_constructor(self): - BasicTestMappingProtocol.test_constructor(self) + BasicTestMutableMappingProtocol.test_constructor(self) self.assertTrue(self._empty_mapping() is not self._empty_mapping()) self.assertEqual(self.type2test(x=1, y=2), {"x": 1, "y": 2}) def test_bool(self): - BasicTestMappingProtocol.test_bool(self) + BasicTestMutableMappingProtocol.test_bool(self) self.assertTrue(not self._empty_mapping()) self.assertTrue(self._full_mapping({"x": "y"})) self.assertTrue(bool(self._empty_mapping()) is False) self.assertTrue(bool(self._full_mapping({"x": "y"})) is True) def test_keys(self): - BasicTestMappingProtocol.test_keys(self) + BasicTestMutableMappingProtocol.test_keys(self) d = self._empty_mapping() self.assertEqual(list(d.keys()), []) d = self._full_mapping({'a': 1, 'b': 2}) @@ -325,12 +332,12 @@ class TestMappingProtocol(BasicTestMappi self.assertNotIn('c', k) def test_values(self): - BasicTestMappingProtocol.test_values(self) + BasicTestMutableMappingProtocol.test_values(self) d = self._full_mapping({1:2}) self.assertEqual(list(d.values()), [2]) def test_items(self): - BasicTestMappingProtocol.test_items(self) + BasicTestMutableMappingProtocol.test_items(self) d = self._full_mapping({1:2}) self.assertEqual(list(d.items()), [(1, 2)]) @@ -348,12 +355,12 @@ class TestMappingProtocol(BasicTestMappi self.assertRaises(TypeError, d.__contains__) def test_len(self): - BasicTestMappingProtocol.test_len(self) + BasicTestMutableMappingProtocol.test_len(self) d = self._full_mapping({'a': 1, 'b': 2}) self.assertEqual(len(d), 2) def test_getitem(self): - BasicTestMappingProtocol.test_getitem(self) + BasicTestMutableMappingProtocol.test_getitem(self) d = self._full_mapping({'a': 1, 'b': 2}) self.assertEqual(d['a'], 1) self.assertEqual(d['b'], 2) @@ -374,7 +381,7 @@ class TestMappingProtocol(BasicTestMappi self.assertRaises(TypeError, d.clear, None) def test_update(self): - BasicTestMappingProtocol.test_update(self) + BasicTestMutableMappingProtocol.test_update(self) # mapping argument d = self._empty_mapping() d.update({1:100}) @@ -475,7 +482,7 @@ class TestMappingProtocol(BasicTestMappi self.assertRaises(TypeError, d.copy, None) def test_get(self): - BasicTestMappingProtocol.test_get(self) + BasicTestMutableMappingProtocol.test_get(self) d = self._empty_mapping() self.assertTrue(d.get('c') is None) self.assertEqual(d.get('c', 3), 3) @@ -486,7 +493,7 @@ class TestMappingProtocol(BasicTestMappi self.assertEqual(d.get('a', 3), 1) def test_setdefault(self): - BasicTestMappingProtocol.test_setdefault(self) + BasicTestMutableMappingProtocol.test_setdefault(self) d = self._empty_mapping() self.assertTrue(d.setdefault('key0') is None) d.setdefault('key0', []) @@ -497,7 +504,7 @@ class TestMappingProtocol(BasicTestMappi self.assertEqual(len(d['key']), 2) def test_popitem(self): - BasicTestMappingProtocol.test_popitem(self) + BasicTestMutableMappingProtocol.test_popitem(self) for copymode in -1, +1: # -1: b has same structure as a # +1: b is a.copy() @@ -521,7 +528,7 @@ class TestMappingProtocol(BasicTestMappi self.assertTrue(not b) def test_pop(self): - BasicTestMappingProtocol.test_pop(self) + BasicTestMutableMappingProtocol.test_pop(self) # Tests for pop with specified key d = self._empty_mapping() diff -r ba51573fcf90 Lib/test/test_dict.py --- a/Lib/test/test_dict.py Mon Feb 27 13:51:02 2012 +0100 +++ b/Lib/test/test_dict.py Wed Feb 29 19:04:31 2012 +0100 @@ -777,22 +777,119 @@ class DictTest(unittest.TestCase): self._tracked(MyDict()) +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()): + frozendict(key=not_hashable) + frozendict(not_hashable=0) + + 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())) + + # not hashable + f = frozendict(x=[]) + self.assertRaises(TypeError, hash, f) + + def test_repr(self): + self.assertEqual(repr(frozendict()), "frozendict({})") + self.assertEqual(repr(frozendict(x=1)), "frozendict({'x': 1})") + + def test_capi(self): + support.import_module('ctypes') + from ctypes import pythonapi, py_object + PyDict_GetItem = getattr(pythonapi, 'PyDict_GetItem') + PyDict_GetItem.argtypes = (py_object, py_object) + PyDict_GetItem.restype = py_object + PyDict_SetItem = getattr(pythonapi, 'PyDict_SetItem') + PyDict_SetItem.argtypes = (py_object, py_object, py_object) + PyDict_SetItem.restype = py_object + PyDict_DelItem = getattr(pythonapi, 'PyDict_DelItem') + PyDict_DelItem.argtypes = (py_object, py_object) + PyDict_DelItem.restype = py_object + + m = frozendict(x=1, y=2) + self.assertEqual(PyDict_GetItem(m, 'x'), 1) + self.assertEqual(PyDict_GetItem(m, 'y'), 2) + + m = frozendict([(1, 'x'), (2, 'y')]) + self.assertEqual(PyDict_GetItem(m, 1), 'x') + self.assertEqual(PyDict_GetItem(m, 2), 'y') + + m = frozendict(x=1) + self.assertRaises(TypeError, PyDict_SetItem, m, 'x') + + m = frozendict() + self.assertRaises(TypeError, PyDict_DelItem, m, 'x', 1) + + from test import mapping_tests -class GeneralMappingTests(mapping_tests.BasicTestMappingProtocol): +class GeneralMappingTests(mapping_tests.BasicTestMutableMappingProtocol): type2test = dict class Dict(dict): pass -class SubclassMappingTests(mapping_tests.BasicTestMappingProtocol): +class SubclassMappingTests(mapping_tests.BasicTestMutableMappingProtocol): type2test = Dict +class FrozenDictMappingTests(mapping_tests.BasicTestMappingProtocol): + type2test = frozendict + +class FrozenDict(frozendict): + pass + +class FrozenDictSubclassMappingTests(mapping_tests.BasicTestMappingProtocol): + type2test = FrozenDict + + def test_main(): support.run_unittest( DictTest, + FrozenDictTests, GeneralMappingTests, SubclassMappingTests, + FrozenDictMappingTests, + FrozenDictSubclassMappingTests, ) if __name__ == "__main__": diff -r ba51573fcf90 Objects/dictobject.c --- a/Objects/dictobject.c Mon Feb 27 13:51:02 2012 +0100 +++ b/Objects/dictobject.c Wed Feb 29 19:04:31 2012 +0100 @@ -10,6 +10,10 @@ #include "Python.h" #include "stringlib/eq.h" +#define _PyBaseDict_Check(op) \ + (PyDict_Check(op) || PyFrozenDict_Check(op)) +#define _PyBaseDict_CheckExact(op) \ + (PyDict_CheckExact(op) || PyFrozenDict_CheckExact(op)) /* Set a key error with the specified argument, wrapping it in a * tuple automatically so that tuple keys are not unpacked as the @@ -25,6 +29,8 @@ set_key_error(PyObject *arg) Py_DECREF(tup); } +static PyObject *frozendict_new(PyTypeObject *type); + /* Define this out if you don't want conversion statistics on exit. */ #undef SHOW_CONVERSION_COUNTS @@ -224,7 +230,7 @@ PyDict_ClearFreeList(void) int ret = numfree; while (numfree) { op = free_list[--numfree]; - assert(PyDict_CheckExact(op)); + assert(_PyBaseDict_CheckExact(op)); PyObject_GC_Del(op); } return ret; @@ -461,7 +467,7 @@ _PyDict_HasOnlyStringKeys(PyObject *dict { Py_ssize_t pos = 0; PyObject *key, *value; - assert(PyDict_Check(dict)); + assert(_PyBaseDict_Check(dict)); /* Shortcut */ if (((PyDictObject *)dict)->ma_lookup == lookdict_unicode) return 1; @@ -500,7 +506,7 @@ _PyDict_MaybeUntrack(PyObject *op) Py_ssize_t mask, i; PyDictEntry *ep; - if (!PyDict_CheckExact(op) || !_PyObject_GC_IS_TRACKED(op)) + if (!_PyBaseDict_CheckExact(op) || !_PyObject_GC_IS_TRACKED(op)) return; mp = (PyDictObject *) op; @@ -725,7 +731,7 @@ PyDict_GetItem(PyObject *op, PyObject *k PyDictObject *mp = (PyDictObject *)op; PyDictEntry *ep; PyThreadState *tstate; - if (!PyDict_Check(op)) + if (!_PyBaseDict_Check(op)) return NULL; if (!PyUnicode_CheckExact(key) || (hash = ((PyASCIIObject *) key)->hash) == -1) @@ -775,7 +781,7 @@ PyDict_GetItemWithError(PyObject *op, Py PyDictObject*mp = (PyDictObject *)op; PyDictEntry *ep; - if (!PyDict_Check(op)) { + if (!_PyBaseDict_Check(op)) { PyErr_BadInternalCall(); return NULL; } @@ -839,13 +845,38 @@ dict_set_item_by_hash_or_entry(register * and occasionally replace a value -- but you can't insert new keys or * remove them. */ +static int +basedict_setitem(PyObject *op, PyObject *key, PyObject *value) +{ + register Py_hash_t hash; + + assert(_PyBaseDict_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); +} + int PyDict_SetItem(register PyObject *op, PyObject *key, PyObject *value) { register Py_hash_t hash; if (!PyDict_Check(op)) { - PyErr_BadInternalCall(); + if (PyFrozenDict_Check(op)) + PyErr_SetString(PyExc_TypeError, + "'frozendict' object does not support item assignment"); + else + PyErr_BadInternalCall(); return -1; } assert(key); @@ -872,7 +903,11 @@ PyDict_DelItem(PyObject *op, PyObject *k PyObject *old_value, *old_key; if (!PyDict_Check(op)) { - PyErr_BadInternalCall(); + if (PyFrozenDict_Check(op)) + PyErr_SetString(PyExc_TypeError, + "'frozendict' object does not support item assignment"); + else + PyErr_BadInternalCall(); return -1; } assert(key); @@ -913,9 +948,10 @@ PyDict_Clear(PyObject *op) Py_ssize_t i, n; #endif - if (!PyDict_Check(op)) + if (!_PyBaseDict_Check(op)) return; mp = (PyDictObject *)op; + #ifdef Py_DEBUG n = mp->ma_mask + 1; i = 0; @@ -992,7 +1028,7 @@ PyDict_Next(PyObject *op, Py_ssize_t *pp register Py_ssize_t mask; register PyDictEntry *ep; - if (!PyDict_Check(op)) + if (!_PyBaseDict_Check(op)) return 0; i = *ppos; if (i < 0) @@ -1019,7 +1055,7 @@ _PyDict_Next(PyObject *op, Py_ssize_t *p register Py_ssize_t mask; register PyDictEntry *ep; - if (!PyDict_Check(op)) + if (!_PyBaseDict_Check(op)) return 0; i = *ppos; if (i < 0) @@ -1143,6 +1179,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) { @@ -1167,7 +1215,7 @@ dict_subscript(PyDictObject *mp, registe return NULL; v = ep->me_value; if (v == NULL) { - if (!PyDict_CheckExact(mp)) { + if (!_PyBaseDict_CheckExact(mp)) { /* Look up __missing__ method if we're a subclass. */ PyObject *missing, *res; _Py_IDENTIFIER(__missing__); @@ -1198,6 +1246,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*/ @@ -1340,7 +1393,7 @@ dict_fromkeys(PyObject *cls, PyObject *a if (d == NULL) return NULL; - if (PyDict_CheckExact(d) && PyDict_CheckExact(seq)) { + if (_PyBaseDict_CheckExact(d) && _PyBaseDict_CheckExact(seq)) { PyDictObject *mp = (PyDictObject *)d; PyObject *oldvalue; Py_ssize_t pos = 0; @@ -1363,7 +1416,7 @@ dict_fromkeys(PyObject *cls, PyObject *a return d; } - if (PyDict_CheckExact(d) && PyAnySet_CheckExact(seq)) { + if (_PyBaseDict_CheckExact(d) && PyAnySet_CheckExact(seq)) { PyDictObject *mp = (PyDictObject *)d; Py_ssize_t pos = 0; PyObject *key; @@ -1391,9 +1444,9 @@ dict_fromkeys(PyObject *cls, PyObject *a return NULL; } - if (PyDict_CheckExact(d)) { + if (_PyBaseDict_CheckExact(d)) { while ((key = PyIter_Next(it)) != NULL) { - status = PyDict_SetItem(d, key, value); + status = basedict_setitem(d, key, value); Py_DECREF(key); if (status < 0) goto Fail; @@ -1470,7 +1523,7 @@ PyDict_MergeFromSeq2(PyObject *d, PyObje PyObject *fast; /* item as a 2-tuple or 2-list */ assert(d != NULL); - assert(PyDict_Check(d)); + assert(_PyBaseDict_Check(d)); assert(seq2 != NULL); it = PyObject_GetIter(seq2); @@ -1512,7 +1565,7 @@ PyDict_MergeFromSeq2(PyObject *d, PyObje key = PySequence_Fast_GET_ITEM(fast, 0); value = PySequence_Fast_GET_ITEM(fast, 1); if (override || PyDict_GetItem(d, key) == NULL) { - int status = PyDict_SetItem(d, key, value); + int status = basedict_setitem(d, key, value); if (status < 0) goto Fail; } @@ -1549,12 +1602,12 @@ 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 || !_PyBaseDict_Check(a) || b == NULL) { PyErr_BadInternalCall(); return -1; } mp = (PyDictObject*)a; - if (PyDict_Check(b)) { + if (_PyBaseDict_Check(b)) { other = (PyDictObject*)b; if (other == mp || other->ma_used == 0) /* a.update(a) or a.update({}); nothing to do */ @@ -1618,7 +1671,7 @@ PyDict_Merge(PyObject *a, PyObject *b, i Py_DECREF(key); return -1; } - status = PyDict_SetItem(a, key, value); + status = basedict_setitem(a, key, value); Py_DECREF(key); Py_DECREF(value); if (status < 0) { @@ -1658,10 +1711,27 @@ PyDict_Copy(PyObject *o) return NULL; } +static PyObject * +frozendict_copy(PyObject *o) +{ + PyObject *copy; + if (o == NULL || !_PyBaseDict_Check(o)) { + PyErr_BadInternalCall(); + return NULL; + } + copy = frozendict_new(&PyFrozenDict_Type); + if (copy == NULL) + return NULL; + if (PyDict_Merge(copy, o, 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 || !_PyBaseDict_Check(mp)) { PyErr_BadInternalCall(); return -1; } @@ -1671,7 +1741,7 @@ PyDict_Size(PyObject *mp) PyObject * PyDict_Keys(PyObject *mp) { - if (mp == NULL || !PyDict_Check(mp)) { + if (mp == NULL || !_PyBaseDict_Check(mp)) { PyErr_BadInternalCall(); return NULL; } @@ -1681,7 +1751,7 @@ PyDict_Keys(PyObject *mp) PyObject * PyDict_Values(PyObject *mp) { - if (mp == NULL || !PyDict_Check(mp)) { + if (mp == NULL || !_PyBaseDict_Check(mp)) { PyErr_BadInternalCall(); return NULL; } @@ -1691,7 +1761,7 @@ PyDict_Values(PyObject *mp) PyObject * PyDict_Items(PyObject *mp) { - if (mp == NULL || !PyDict_Check(mp)) { + if (mp == NULL || !_PyBaseDict_Check(mp)) { PyErr_BadInternalCall(); return NULL; } @@ -1746,7 +1816,7 @@ dict_richcompare(PyObject *v, PyObject * int cmp; PyObject *res; - if (!PyDict_Check(v) || !PyDict_Check(w)) { + if (!_PyBaseDict_Check(v) || !_PyBaseDict_Check(w)) { res = Py_NotImplemented; } else if (op == Py_EQ || op == Py_NE) { @@ -2034,7 +2104,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)dict_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,6 +2230,37 @@ dict_new(PyTypeObject *type, PyObject *a return self; } +static PyObject * +frozendict_new(PyTypeObject *type) +{ + PyFrozenDictObject *self; + + self = (PyFrozenDictObject *)dict_new(type, NULL, NULL); + if (self == NULL) + return NULL; + self->hash = -1; + return (PyObject*)self; +} + +static PyObject * +frozendict___new__(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyObject *self = frozendict_new(type); + if (self == NULL) + return NULL; + if (dict_update_common(self, args, kwds, "frozendict") == -1) { + Py_DECREF(self); + return NULL; + } + return self; +} + +PyObject * +PyDict_AsFrozen(PyObject *dict) +{ + return frozendict_copy(dict); +} + static int dict_init(PyObject *self, PyObject *args, PyObject *kwds) { @@ -2150,6 +2273,86 @@ dict_iter(PyDictObject *dict) return dictiter_new(dict, &PyDictIterKey_Type); } +static Py_hash_t +frozendict_hash(PyObject *self) +{ + PyFrozenDictObject *frozendict = (PyFrozenDictObject *)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(PyFrozenDictObject), + 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 +2393,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 +2527,7 @@ static PyObject *dictiter_iternextkey(di if (d == NULL) return NULL; - assert (PyDict_Check(d)); + assert (_PyBaseDict_Check(d)); if (di->di_used != d->ma_used) { PyErr_SetString(PyExc_RuntimeError, @@ -2396,7 +2599,7 @@ static PyObject *dictiter_iternextvalue( if (d == NULL) return NULL; - assert (PyDict_Check(d)); + assert (_PyBaseDict_Check(d)); if (di->di_used != d->ma_used) { PyErr_SetString(PyExc_RuntimeError, @@ -2468,7 +2671,7 @@ static PyObject *dictiter_iternextitem(d if (d == NULL) return NULL; - assert (PyDict_Check(d)); + assert (_PyBaseDict_Check(d)); if (di->di_used != d->ma_used) { PyErr_SetString(PyExc_RuntimeError, @@ -2589,7 +2792,7 @@ dictview_new(PyObject *dict, PyTypeObjec PyErr_BadInternalCall(); return NULL; } - if (!PyDict_Check(dict)) { + if (!_PyBaseDict_Check(dict)) { /* XXX Get rid of this restriction later */ PyErr_Format(PyExc_TypeError, "%s() requires a dict argument, not '%s'", diff -r ba51573fcf90 Objects/object.c --- a/Objects/object.c Mon Feb 27 13:51:02 2012 +0100 +++ b/Objects/object.c Wed Feb 29 19:04:31 2012 +0100 @@ -1623,6 +1623,9 @@ _Py_ReadyTypes(void) if (PyType_Ready(&PyDict_Type) < 0) Py_FatalError("Can't initialize dict type"); + if (PyType_Ready(&PyFrozenDict_Type) < 0) + Py_FatalError("Can't initialize frozendict type"); + if (PyType_Ready(&PySet_Type) < 0) Py_FatalError("Can't initialize set type"); diff -r ba51573fcf90 Python/bltinmodule.c --- a/Python/bltinmodule.c Mon Feb 27 13:51:02 2012 +0100 +++ b/Python/bltinmodule.c Wed Feb 29 19:04:31 2012 +0100 @@ -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); diff -r ba51573fcf90 Tools/gdb/libpython.py --- a/Tools/gdb/libpython.py Mon Feb 27 13:51:02 2012 +0100 +++ b/Tools/gdb/libpython.py Wed Feb 29 19:04:31 2012 +0100 @@ -331,6 +331,7 @@ class PyObjectPtr(object): 'frame': PyFrameObjectPtr, 'set' : PySetObjectPtr, 'frozenset' : PySetObjectPtr, + 'frozendict' : PyFrozenDictObjectPtr, 'builtin_function_or_method' : PyCFunctionObjectPtr, } if tp_name in name_map: @@ -672,6 +673,25 @@ class PyDictObjectPtr(PyObjectPtr): pyop_value.write_repr(out, visited) out.write('}') +class PyFrozenDictObjectPtr(PyDictObjectPtr): + """ + Class wrapping a gdb.Value that's a PyFrozenDictObject* i.e. a frozendict + instance within the process being debugged. + """ + _typename = 'PyFrozenDictObject' + + def iteritems(self): + ''' + Yields a sequence of (PyObjectPtr key, PyObjectPtr value) pairs, + analagous to dict.iteritems() + ''' + for i in safe_range(self.field('_base')['ma_mask'] + 1): + ep = self.field('_base')['ma_table'] + i + pyop_value = PyObjectPtr.from_pyobject_ptr(ep['me_value']) + if not pyop_value.is_null(): + pyop_key = PyObjectPtr.from_pyobject_ptr(ep['me_key']) + yield (pyop_key, pyop_value) + class PyListObjectPtr(PyObjectPtr): _typename = 'PyListObject'