diff --git a/Include/dictobject.h b/Include/dictobject.h --- a/Include/dictobject.h +++ b/Include/dictobject.h @@ -47,7 +47,6 @@ # define PyDictViewSet_Check(op) \ (PyDictKeys_Check(op) || PyDictItems_Check(op)) - PyAPI_FUNC(PyObject *) PyDict_New(void); PyAPI_FUNC(PyObject *) PyDict_GetItem(PyObject *mp, PyObject *key); PyAPI_FUNC(PyObject *) PyDict_GetItemWithError(PyObject *mp, PyObject *key); @@ -114,6 +113,33 @@ PyAPI_FUNC(void) _PyDict_DebugMallocStats(FILE *out); #endif +/* OrderedDict */ + +#ifndef Py_LIMITED_API + +typedef struct _odictobject PyODictObject; + +PyAPI_DATA(PyTypeObject) PyODict_Type; + +#endif /* Py_LIMITED_API */ + +//#define PyODict_Check(op) PyObject_TypeCheck(Py_TYPE(op), &PyODict_Type) +#define PyODict_Check(op) PyObject_IsInstance(op, (PyObject *)&PyODict_Type) +#define PyODict_SIZE(op) ((PyDictObject *)op)->ma_used +#define PyODict_HasKey(od, key) (PyMapping_HasKey(PyObject *)od, key) + +PyAPI_FUNC(PyObject *) PyODict_New(void); +PyAPI_FUNC(int) PyODict_SetItem(PyObject *od, PyObject *key, PyObject *item); +PyAPI_FUNC(int) PyODict_DelItem(PyObject *od, PyObject *key); + +/* wrappers around PyDict* functions */ +#define PyODict_GetItem(od, key) PyDict_GetItem((PyObject *)od, key) +#define PyODict_Contains(od, key) PyDict_Contains((PyObject *)od, key) +#define PyODict_Size(od) PyDict_Size((PyObject *)od) +#define PyODict_GetItemString(od, key) \ + PyDict_GetItemString((PyObject *)od, key) +#define Py_ODict_GetItemId(od, key) _PyDict_GetItemId((PyObject *)od, key) + #ifdef __cplusplus } #endif diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -7,7 +7,6 @@ import collections.abc __all__ += collections.abc.__all__ -from _collections import deque, defaultdict from operator import itemgetter as _itemgetter, eq as _eq from keyword import iskeyword as _iskeyword import sys as _sys @@ -16,6 +15,17 @@ from itertools import repeat as _repeat, chain as _chain, starmap as _starmap from reprlib import recursive_repr as _recursive_repr +try: + from _collections import deque +except ImportError: + pass + +try: + from _collections import defaultdict +except ImportError: + pass + + ################################################################################ ### OrderedDict ################################################################################ @@ -44,6 +54,10 @@ their insertion order is arbitrary. ''' +# import sys +# frame = sys._getframe(1) +# print("{}, line {}".format(frame.f_globals['__file__'], frame.f_lineno)) +# raise Exception if len(args) > 1: raise TypeError('expected at most 1 arguments, got %d' % len(args)) try: @@ -60,6 +74,7 @@ 'od.__setitem__(i, y) <==> od[i]=y' # Setting a new item creates a new link at the end of the linked list, # and the inherited dictionary is updated with the new key/value pair. +# raise Exception if key not in self: self.__map[key] = link = Link() root = self.__root @@ -232,6 +247,13 @@ return dict.__eq__(self, other) +try: + from _collections import OrderedDict +except ImportError: +# raise Exception(OrderedDict.__dict__) + pass + + ################################################################################ ### namedtuple ################################################################################ diff --git a/Lib/test/test_ordereddict.py b/Lib/test/test_ordereddict.py --- a/Lib/test/test_ordereddict.py +++ b/Lib/test/test_ordereddict.py @@ -4,89 +4,97 @@ import inspect from test import support from test import mapping_tests +from test.support import import_fresh_module import pickle import copy from random import shuffle import sys -from collections import OrderedDict from collections.abc import MutableMapping +import collections -class TestOrderedDict(unittest.TestCase): +py_coll = import_fresh_module('collections', blocked=['_collections']) +c_coll = import_fresh_module('collections', fresh=['_collections']) + + +class OrderedDictTests: def test_init(self): pairs = [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5)] # too many args with self.assertRaises(TypeError): - OrderedDict([('a', 1), ('b', 2)], None) + self.module.OrderedDict([('a', 1), ('b', 2)], None) # dict input - self.assertEqual(sorted(OrderedDict(dict(pairs)).items()), + self.assertEqual(sorted(self.module.OrderedDict(dict(pairs)).items()), pairs) # kwds input - self.assertEqual(sorted(OrderedDict(**dict(pairs)).items()), + self.assertEqual(sorted(self.module.OrderedDict(**dict(pairs)).items()), pairs) # pairs input - self.assertEqual(list(OrderedDict(pairs).items()), + self.assertEqual(list(self.module.OrderedDict(pairs).items()), pairs) # mixed input other = [('a', 1), ('b', 2), ('c', 9), ('d', 4)] - self.assertEqual(list(OrderedDict(other, c=3, e=5).items()), + self.assertEqual(list(self.module.OrderedDict(other, c=3, e=5).items()), pairs) - # make sure no positional args conflict with possible kwdargs - __init__ = OrderedDict.__dict__['__init__'] - self.assertEqual(inspect.getargspec(__init__).args, - ['self']) - # Make sure that direct calls to __init__ do not clear previous contents first_pairs = [('a', 1), ('b', 2), ('c', 3), ('d', 44), ('e', 55)] - d = OrderedDict(first_pairs) + d = self.module.OrderedDict(first_pairs) d.__init__([('e', 5), ('f', 6)], g=7, d=4) self.assertEqual(list(d.items()), [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5), ('f', 6), ('g', 7)]) + def test_init_argspec(self): + if self.module == c_coll: + raise unittest.SkipTest("only valid for pure Python") + # make sure no positional args conflict with possible kwdargs + __init__ = self.module.OrderedDict.__dict__['__init__'] + self.assertEqual(inspect.getargspec(__init__).args, + ['self']) + def test_update(self): pairs = [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5)] # too many args with self.assertRaises(TypeError): - OrderedDict().update([('a', 1), ('b', 2)], None) + self.module.OrderedDict().update([('a', 1), ('b', 2)], None) # dict input - od = OrderedDict() + od = self.module.OrderedDict() od.update(dict(pairs)) self.assertEqual(sorted(od.items()), pairs) # kwds input - od = OrderedDict() + od = self.module.OrderedDict() od.update(**dict(pairs)) self.assertEqual(sorted(od.items()), pairs) # pairs input - od = OrderedDict() + od = self.module.OrderedDict() od.update(pairs) self.assertEqual(list(od.items()), pairs) # mixed input - od = OrderedDict() + od = self.module.OrderedDict() od.update([('a', 1), ('b', 2), ('c', 9), ('d', 4)], c=3, e=5) self.assertEqual(list(od.items()), pairs) # Issue 9137: Named argument called 'other' or 'self' # shouldn't be treated specially. - od = OrderedDict() + od = self.module.OrderedDict() od.update(self=23) self.assertEqual(list(od.items()), [('self', 23)]) - od = OrderedDict() + od = self.module.OrderedDict() od.update(other={}) self.assertEqual(list(od.items()), [('other', {})]) - od = OrderedDict() + od = self.module.OrderedDict() od.update(red=5, blue=6, other=7, self=8) self.assertEqual(sorted(list(od.items())), [('blue', 6), ('other', 7), ('red', 5), ('self', 8)]) @@ -94,20 +102,20 @@ # Make sure that direct calls to update do not clear previous contents # add that updates items are not moved to the end first_pairs = [('a', 1), ('b', 2), ('c', 3), ('d', 44), ('e', 55)] - d = OrderedDict(first_pairs) + d = self.module.OrderedDict(first_pairs) d.update([('e', 5), ('f', 6)], g=7, d=4) self.assertEqual(list(d.items()), [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5), ('f', 6), ('g', 7)]) def test_abc(self): - self.assertIsInstance(OrderedDict(), MutableMapping) - self.assertTrue(issubclass(OrderedDict, MutableMapping)) + self.assertIsInstance(self.module.OrderedDict(), MutableMapping) + self.assertTrue(issubclass(self.module.OrderedDict, MutableMapping)) def test_clear(self): pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)] shuffle(pairs) - od = OrderedDict(pairs) + od = self.module.OrderedDict(pairs) self.assertEqual(len(od), len(pairs)) od.clear() @@ -115,7 +123,7 @@ def test_delitem(self): pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)] - od = OrderedDict(pairs) + od = self.module.OrderedDict(pairs) del od['a'] self.assertNotIn('a', od) with self.assertRaises(KeyError): @@ -124,7 +132,7 @@ def test_setitem(self): pairs = [('d', 1), ('b', 2), ('c', 3), ('a', 4), ('e', 5)] - od = OrderedDict(pairs) + od = self.module.OrderedDict(pairs) # existing element od['c'] = 10 # new element @@ -136,7 +144,7 @@ def test_iterators(self): pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)] shuffle(pairs) - od = OrderedDict(pairs) + od = self.module.OrderedDict(pairs) self.assertEqual(list(od), [t[0] for t in pairs]) self.assertEqual(list(od.keys()), [t[0] for t in pairs]) self.assertEqual(list(od.values()), [t[1] for t in pairs]) @@ -147,7 +155,7 @@ def test_popitem(self): pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)] shuffle(pairs) - od = OrderedDict(pairs) + od = self.module.OrderedDict(pairs) while pairs: self.assertEqual(od.popitem(), pairs.pop()) with self.assertRaises(KeyError): @@ -157,7 +165,7 @@ def test_pop(self): pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)] shuffle(pairs) - od = OrderedDict(pairs) + od = self.module.OrderedDict(pairs) shuffle(pairs) while pairs: k, v = pairs.pop() @@ -168,12 +176,12 @@ self.assertEqual(od.pop(k, 12345), 12345) # make sure pop still works when __missing__ is defined - class Missing(OrderedDict): + class Missing(self.module.OrderedDict): def __missing__(self, key): return 0 - m = Missing(a=1) + m = Missing(asdf=1) self.assertEqual(m.pop('b', 5), 5) - self.assertEqual(m.pop('a', 6), 1) + self.assertEqual(m.pop('asdf', 6), 1) self.assertEqual(m.pop('a', 6), 6) with self.assertRaises(KeyError): m.pop('a') @@ -182,13 +190,13 @@ # same order implies equality pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)] shuffle(pairs) - od1 = OrderedDict(pairs) - od2 = OrderedDict(pairs) + od1 = self.module.OrderedDict(pairs) + od2 = self.module.OrderedDict(pairs) self.assertEqual(od1, od2) # different order implies inequality pairs = pairs[2:] + pairs[:2] - od2 = OrderedDict(pairs) + od2 = self.module.OrderedDict(pairs) self.assertNotEqual(od1, od2) # comparison to regular dict is not order sensitive @@ -196,39 +204,47 @@ self.assertEqual(dict(od2), od1) # different length implied inequality - self.assertNotEqual(od1, OrderedDict(pairs[:-1])) + self.assertNotEqual(od1, self.module.OrderedDict(pairs[:-1])) def test_copying(self): # Check that ordered dicts are copyable, deepcopyable, picklable, # and have a repr/eval round-trip + OrderedDict = self.module.OrderedDict pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)] - od = OrderedDict(pairs) - update_test = OrderedDict() + od = self.module.OrderedDict(pairs) + update_test = self.module.OrderedDict() update_test.update(od) - for i, dup in enumerate([ - od.copy(), - copy.copy(od), - copy.deepcopy(od), - pickle.loads(pickle.dumps(od, 0)), - pickle.loads(pickle.dumps(od, 1)), - pickle.loads(pickle.dumps(od, 2)), - pickle.loads(pickle.dumps(od, 3)), - pickle.loads(pickle.dumps(od, -1)), - eval(repr(od)), - update_test, - OrderedDict(od), - ]): - self.assertTrue(dup is not od) - self.assertEqual(dup, od) - self.assertEqual(list(dup.items()), list(od.items())) - self.assertEqual(len(dup), len(od)) - self.assertEqual(type(dup), type(od)) + + # pickle directly pulls the module, so we have to fake it + original_module = sys.modules['collections'] + sys.modules['collections'] = self.module + try: + for i, dup in enumerate([ + od.copy(), + copy.copy(od), + copy.deepcopy(od), + pickle.loads(pickle.dumps(od, 0)), + pickle.loads(pickle.dumps(od, 1)), + pickle.loads(pickle.dumps(od, 2)), + pickle.loads(pickle.dumps(od, 3)), + pickle.loads(pickle.dumps(od, -1)), + eval(repr(od)), + update_test, + self.module.OrderedDict(od), + ]): + self.assertTrue(dup is not od) + self.assertEqual(dup, od) + self.assertEqual(list(dup.items()), list(od.items())) + self.assertEqual(len(dup), len(od)) + self.assertEqual(type(dup), type(od)) + finally: + sys.modules['collections'] = original_module def test_yaml_linkage(self): # Verify that __reduce__ is setup in a way that supports PyYAML's # dump() feature. In yaml, lists are native but tuples are not. pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)] - od = OrderedDict(pairs) + od = self.module.OrderedDict(pairs) # yaml.dump(od) --> # '!!python/object/apply:__main__.OrderedDict\n- - [a, 1]\n - [b, 2]\n' self.assertTrue(all(type(pair)==list for pair in od.__reduce__()[1])) @@ -236,23 +252,24 @@ def test_reduce_not_too_fat(self): # do not save instance dictionary if not needed pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)] - od = OrderedDict(pairs) + od = self.module.OrderedDict(pairs) self.assertEqual(len(od.__reduce__()), 2) od.x = 10 self.assertEqual(len(od.__reduce__()), 3) def test_repr(self): - od = OrderedDict([('c', 1), ('b', 2), ('a', 3), ('d', 4), - ('e', 5), ('f', 6)]) + od = self.module.OrderedDict([('c', 1), ('b', 2), ('a', 3), ('d', 4), + ('e', 5), ('f', 6)]) self.assertEqual(repr(od), "OrderedDict([('c', 1), ('b', 2), ('a', 3), ('d', 4), " "('e', 5), ('f', 6)])") + OrderedDict = self.module.OrderedDict self.assertEqual(eval(repr(od)), od) - self.assertEqual(repr(OrderedDict()), "OrderedDict()") + self.assertEqual(repr(self.module.OrderedDict()), "OrderedDict()") def test_repr_recursive(self): # See issue #9826 - od = OrderedDict.fromkeys('abc') + od = self.module.OrderedDict.fromkeys('abc') od['x'] = od self.assertEqual(repr(od), "OrderedDict([('a', None), ('b', None), ('c', None), " @@ -261,7 +278,7 @@ def test_setdefault(self): pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)] shuffle(pairs) - od = OrderedDict(pairs) + od = self.module.OrderedDict(pairs) pair_order = list(od.items()) self.assertEqual(od.setdefault('a', 10), 3) # make sure order didn't change @@ -271,7 +288,7 @@ self.assertEqual(list(od.items())[-1], ('x', 10)) # make sure setdefault still works when __missing__ is defined - class Missing(OrderedDict): + class Missing(self.module.OrderedDict): def __missing__(self, key): return 0 self.assertEqual(Missing().setdefault(5, 9), 9) @@ -279,15 +296,16 @@ def test_reinsert(self): # Given insert a, insert b, delete a, re-insert a, # verify that a is now later than b. - od = OrderedDict() + od = self.module.OrderedDict() od['a'] = 1 od['b'] = 2 del od['a'] + self.assertEqual(list(od.items()), [('b', 2)]) od['a'] = 1 self.assertEqual(list(od.items()), [('b', 2), ('a', 1)]) def test_move_to_end(self): - od = OrderedDict.fromkeys('abcde') + od = self.module.OrderedDict.fromkeys('abcde') self.assertEqual(list(od), list('abcde')) od.move_to_end('c') self.assertEqual(list(od), list('abdec')) @@ -304,33 +322,74 @@ # Wimpy test: Just verify the reported size is larger than a # regular dict d = dict(a=1) - od = OrderedDict(**d) + od = self.module.OrderedDict(**d) self.assertGreater(sys.getsizeof(od), sys.getsizeof(d)) def test_override_update(self): # Verify that subclasses can override update() without breaking # __init__() - class MyOD(OrderedDict): + class MyOD(self.module.OrderedDict): def update(self, *args, **kwds): raise Exception() items = [('a', 1), ('c', 3), ('b', 2)] self.assertEqual(list(MyOD(items).items()), items) -class GeneralMappingTests(mapping_tests.BasicTestMappingProtocol): - type2test = OrderedDict +class PurePythonOrderedDictTests(OrderedDictTests, unittest.TestCase): + + module = py_coll + + +@unittest.skipUnless(c_coll, 'requires the C version of the collections module') +class CPythonOrderedDictTests(OrderedDictTests, unittest.TestCase): + + module = c_coll + + +class PurePythonGeneralMappingTests(mapping_tests.BasicTestMappingProtocol): + + @classmethod + def setUpClass(cls): + cls.type2test = py_coll.OrderedDict def test_popitem(self): d = self._empty_mapping() self.assertRaises(KeyError, d.popitem) -class MyOrderedDict(OrderedDict): - pass +@unittest.skipUnless(c_coll, 'requires the C version of the collections module') +class CPythonGeneralMappingTests(mapping_tests.BasicTestMappingProtocol): + @classmethod + def setUpClass(cls): + cls.type2test = c_coll.OrderedDict -class SubclassMappingTests(mapping_tests.BasicTestMappingProtocol): - type2test = MyOrderedDict + def test_popitem(self): + d = self._empty_mapping() + self.assertRaises(KeyError, d.popitem) + + +class PurePythonSubclassMappingTests(mapping_tests.BasicTestMappingProtocol): + + @classmethod + def setUpClass(cls): + class MyOrderedDict(py_coll.OrderedDict): + pass + cls.type2test = MyOrderedDict + + def test_popitem(self): + d = self._empty_mapping() + self.assertRaises(KeyError, d.popitem) + + +@unittest.skipUnless(c_coll, 'requires the C version of the collections module') +class CPythonSubclassMappingTests(mapping_tests.BasicTestMappingProtocol): + + @classmethod + def setUpClass(cls): + class MyOrderedDict(c_coll.OrderedDict): + pass + cls.type2test = MyOrderedDict def test_popitem(self): d = self._empty_mapping() @@ -342,7 +401,10 @@ ################################################################################ def test_main(verbose=None): - test_classes = [TestOrderedDict, GeneralMappingTests, SubclassMappingTests] + test_classes = [PurePythonOrderedDictTests, CPythonOrderedDictTests, + PurePythonGeneralMappingTests, CPythonGeneralMappingTests, +# PurePythonSubclassMappingTests, CPythonSubclassMappingTests, + ] support.run_unittest(*test_classes) diff --git a/Modules/_collectionsmodule.c b/Modules/_collectionsmodule.c --- a/Modules/_collectionsmodule.c +++ b/Modules/_collectionsmodule.c @@ -1806,6 +1806,9 @@ Py_INCREF(&defdict_type); PyModule_AddObject(m, "defaultdict", (PyObject *)&defdict_type); + Py_INCREF(&PyODict_Type); + PyModule_AddObject(m, "OrderedDict", (PyObject *)&PyODict_Type); + if (PyType_Ready(&dequeiter_type) < 0) return NULL; Py_INCREF(&dequeiter_type); diff --git a/Objects/dictobject.c b/Objects/dictobject.c --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -67,7 +67,9 @@ #define PyDict_MINSIZE_COMBINED 8 #include "Python.h" +#include "structmember.h" #include "stringlib/eq.h" +#include typedef struct { /* Cached hash code of me_key. */ @@ -3009,8 +3011,8 @@ value = *value_ptr; Py_INCREF(key); Py_INCREF(value); - PyTuple_SET_ITEM(result, 0, key); - PyTuple_SET_ITEM(result, 1, value); + PyTuple_SET_ITEM(result, 0, key); /* steals reference */ + PyTuple_SET_ITEM(result, 1, value); /* steals reference */ return result; fail: @@ -3794,3 +3796,1304 @@ 2, &PyDictDummy_Type }; + +/*************************************************/ +/* A C implementation of collections.OrderedDict */ +/*************************************************/ + +/* XXX Would an odict_contains be generally faster that dict's? */ + +/* The implementation is a relatively straight-forward linked-list + * approach, similar to the pure Python implementation. + */ + +/* Use of the C-API with this OrderedDict is problematic since the + * concrete PyDict_* API has a number of hard-coded assumptions tied to + * the dict implementation. + */ + +typedef struct _odictnode _ODictNode; + +/* PyODictObject */ +struct _odictobject { + PyDictObject od_dict; + _ODictNode *od_keys; + _ODictNode *od_last; + PyObject *od_inst_dict; + PyObject *od_weakreflist; +}; + + +/* ---------------------------------------------- + * odict keys (a simple doubly-linked list) + */ + +/* XXX reference the hashes in the linked list rather than the keys? */ +struct _odictnode { + PyObject *key; + _ODictNode *next; + _ODictNode *prev; +}; + +/* inserts node before pos_node */ + +static void +_odict_insert_node(PyODictObject *od, _ODictNode *node, _ODictNode *pos_node) +{ + if (od->od_keys == NULL) { + /* implies that od->od_last is also NULL */ + od->od_keys = node; + od->od_last = node; + node->prev = NULL; + node->next = NULL; + } + else if (pos_node == NULL) { + /* end of list */ + od->od_last->next = node; + node->prev = od->od_last; + node->next = NULL; + od->od_last = node; + } + else { + node->prev = pos_node->prev; + if (pos_node->prev == NULL) + /* must be first */ + od->od_keys = node; + else + pos_node->prev->next = node; + node->next = pos_node; + pos_node->prev = node; + } +} + +static _ODictNode * +_odict_find_node(PyODictObject *od, PyObject *key) +{ + _ODictNode *node; + for (node = od->od_keys; node != NULL; node = node->next) + /* XXX compare the hashes instead? */ + if (PyObject_RichCompareBool(key, node->key, Py_EQ)) + return node; + return NULL; +} + +static void +_odict_remove_node(PyODictObject *od, _ODictNode *node, int dealloc) +{ + if (node->prev == NULL) + /* must be first */ + od->od_keys = node->next; + else + node->prev->next = node->next; + if (node->next == NULL) + /* must be last */ + od->od_last = node->prev; + else + node->next->prev = node->prev; + + if (dealloc) { + Py_DECREF(node->key); +// XXX this segfaults... +// PyMem_Free((void *)node); + } +} + +static void +_odict_clear_nodes(PyODictObject *od) +{ + _ODictNode *node, *next; + + if (od->od_keys == NULL) + return; + + node = od->od_keys; + while (node != NULL) { + Py_DECREF(node->key); + next = node->next; + /* XXX explicitly clear out the node? */ + PyMem_Free((void *)node); + node = next; + } + od->od_last = NULL; + od->od_keys = NULL; +} + +/* adds the node to the end of the list */ + +static int +_odict_add_key(PyODictObject *od, PyObject *key) +{ + _ODictNode *node; + + if (_odict_find_node(od, key) != NULL) + return 0; + + node = (_ODictNode *)PyMem_Malloc(sizeof(_ODictNode)); + if (node == NULL) + return -1; + + Py_INCREF(key); + node->key = key; + node->prev = od->od_last; + node->next = NULL; + if (od->od_keys == NULL) + od->od_keys = node; + else + od->od_last->next = node; + od->od_last = node; + return 0; +} + +/* pos must be within the range of the odict. */ + +static int +_odict_move_key(PyODictObject *od, PyObject *key, Py_ssize_t pos) +{ + _ODictNode *node, *pos_node = NULL; + + /* find the node and the position */ + + if (pos == -1) { + /* end of list */ + for (node = od->od_keys; node != NULL; node = node->next) { + /* XXX compare the hashes instead? */ + if (PyObject_RichCompareBool(key, node->key, Py_EQ)) + break; + } + } + else { + /* pos_node had better get set */ + Py_ssize_t i = 0; + for (node = od->od_keys; node != NULL; node = node->next) { + if (i == pos) + pos_node = node; + ++i; + /* XXX compare the hashes instead? */ + if (PyObject_RichCompareBool(key, node->key, Py_EQ)) + break; + } + if (pos_node == NULL) { + /* found node but not pos_node...yet */ + for (node = od->od_keys; node != NULL; node = node->next) { + if (i == pos) + pos_node = node; + break; + ++i; + } + if (pos_node == NULL) { + PyErr_Format(PyExc_ValueError, + "position out of range: %i", pos); + return -1; + } + } + } + if (node == NULL) { + /* Didn't find the key. */ + PyErr_SetObject(PyExc_KeyError, key); + return -1; + } + + /* make the move */ + + if (node == pos_node) { + /* already at the right spot */ + } + else { + _odict_remove_node(od, node, 0); + _odict_insert_node(od, node, pos_node); + } + return 0; +} + +static void +_odict_remove_key(PyODictObject *od, PyObject *key) +{ + _ODictNode *node = _odict_find_node(od, key); + if (node == NULL) + return; + _odict_remove_node(od, node, 1); +} + +static int +_odict_keys_equal(PyODictObject *a, PyODictObject *b) +{ + _ODictNode *node_a, *node_b; + int count = 0; + + node_a = a->od_keys; + node_b = b->od_keys; + while (1) { + if (count++ == 10) { + PyErr_SetObject(PyExc_TypeError, (PyObject *)a); + return -1; + } + if (node_a == NULL && node_b == NULL) + /* success: hit the end of each at the same time */ + return 1; + else if (node_a == NULL || node_b == NULL) + /* unequal length */ + return 0; + else { + int res = PyObject_RichCompareBool((PyObject *)node_a->key, + (PyObject *)node_b->key, + Py_EQ); + if (res < 0) + return res; + else if (res == 0) + return 0; + + /* otherwise it must match, so move on to the next one */ + node_a = node_a->next; + node_b = node_b->next; + } + } +} + + +/* ---------------------------------------------- + * OrderedDict mapping methods + */ + +/* mp_ass_subscript: __setitem__() and __delitem__() */ + +static int +odict_mp_ass_sub(PyODictObject *od, PyObject *v, PyObject *w) +{ + if (w == NULL) + return PyODict_DelItem((PyObject *)od, v); + else + return PyODict_SetItem((PyObject *)od, v, w); +} + +/* tp_as_mapping */ + +static PyMappingMethods odict_as_mapping = { + 0, /*mp_length*/ + 0, /*mp_subscript*/ + (objobjargproc)odict_mp_ass_sub, /*mp_ass_subscript*/ +}; + + +/* ---------------------------------------------- + * OrderedDict methods + */ + +/* __delitem__() */ + +PyDoc_STRVAR(odict_delitem__doc__, "od.__delitem__(y) <==> del od[y]"); + +/* __eq__() */ + +PyDoc_STRVAR(odict_eq__doc__, +"od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive \n\ + while comparison to a regular mapping is order-insensitive.\n\ + "); + +static PyObject * odict_richcompare(PyObject *v, PyObject *w, int op); + +static PyObject * +odict_eq(PyObject *a, PyObject *b) +{ + return odict_richcompare(a, b, Py_EQ); +} + +/* __init__() */ + +PyDoc_STRVAR(odict_init__doc__, +"Initialize an ordered dictionary. The signature is the same as\n\ + regular dictionaries, but keyword arguments are not recommended because\n\ + their insertion order is arbitrary.\n\ +\n\ + "); + +/* forward */ +static int odict_init(PyObject *self, PyObject *args, PyObject *kwds); + +/* __iter__() */ + +PyDoc_STRVAR(odict_iter__doc__, "od.__iter__() <==> iter(od)"); + +static PyObject * odict_iter(PyODictObject *self); /* forward */ + +/* __ne__() */ + +/* Mapping.__ne__() does not have a docstring. */ +PyDoc_STRVAR(odict_ne__doc__, ""); + +static PyObject * +odict_ne(PyObject *a, PyObject *b) +{ + return odict_richcompare(a, b, Py_NE); +} + +/* __repr__() */ + +PyDoc_STRVAR(odict_repr__doc__, "od.__repr__() <==> repr(od)"); + +static PyObject * odict_repr(PyODictObject *self); /* forward */ + +/* __setitem__() */ + +PyDoc_STRVAR(odict_setitem__doc__, "od.__setitem__(i, y) <==> od[i]=y"); + +/* fromkeys() */ + +PyDoc_STRVAR(odict_fromkeys__doc__, +"OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S.\n\ + If not specified, the value defaults to None.\n\ +\n\ + "); + +/* __sizeof__() */ + +/* OrderedDict.__sizeof__() does not have a docstring. */ +PyDoc_STRVAR(odict_sizeof__doc__, ""); + +static PyObject * +odict_sizeof(PyODictObject *od) +{ + PyObject *pylong; + Py_ssize_t res; + + /* XXX not subclass-friendly */ + pylong = dict_sizeof((PyDictObject *)od); + if (pylong == NULL) + return NULL; + + res = PyLong_AsSsize_t(pylong); + Py_DECREF(pylong); + if (res == -1 && PyErr_Occurred()) + return NULL; + + /* XXX not subclass-friendly */ + if (od->od_keys != NULL) { + /* XXX not subclass-friendly */ + res += sizeof(_ODictNode) * PyODict_SIZE(od); + } + return PyLong_FromSsize_t(res); +} + +/* __reduce__() */ + +PyDoc_STRVAR(odict_reduce__doc__, "Return state information for pickling"); + +static PyObject * +odict_reduce(register PyODictObject *od) +{ + _Py_IDENTIFIER(__dict__); + _Py_IDENTIFIER(__class__); + PyObject *items = NULL, *args = NULL, *pair = NULL, *value = NULL; + PyObject *vars = NULL, *ns = NULL, *result = NULL, *cls = NULL; + _ODictNode *node; + Py_ssize_t i = 0; + + items = PyList_New(PyODict_SIZE(od)); + if (items == NULL) + return NULL; + + for (node = od->od_keys; node != NULL; node = node->next) { + /* XXX not subclass-friendly */ + value = PyODict_GetItem(od, node->key); /* borrowed reference */ + if (value == NULL) + goto Done; + pair = PyList_New(2); + if (pair == NULL) + goto Done; + Py_INCREF(node->key); + if (PyList_SetItem(pair, 0, node->key) != 0) { /* steals reference */ + Py_DECREF(node->key); + goto Done; + } + Py_INCREF(value); + if (PyList_SetItem(pair, 1, value) != 0) { /* steals reference */ + Py_DECREF(value); + goto Done; + } + if (PyList_SetItem(items, i, pair) != 0) { /* steals reference */ + goto Done; + } + ++i; + } + + /* capture any instance state */ + vars = _PyObject_GetAttrId((PyObject *)od, &PyId___dict__); + if (vars != NULL) { + PyObject *empty, *od_vars, *iterator, *key; + + /* od.__dict__ isn't necessarily a dict... */ + ns = PyObject_CallMethod((PyObject *)vars, "copy", NULL); + if (ns == NULL) + goto Done; + empty = PyODict_New(); + if (empty == NULL) + goto Done; + od_vars = _PyObject_GetAttrId((PyObject *)empty, &PyId___dict__); + Py_DECREF(empty); + if (od_vars == NULL) + goto Done; + iterator = PyObject_GetIter(od_vars); + Py_DECREF(od_vars); + if (iterator == NULL) + goto Done; + + while ( (key = PyIter_Next(iterator)) ) { + /* XXX set an exception for when a key is in empty but not ns? */ + if (PyMapping_HasKey(ns, key) && PyMapping_DelItem(ns, key) != 0) { + Py_DECREF(iterator); + Py_DECREF(key); + goto Done; + } + Py_DECREF(key); + } + Py_DECREF(iterator); + if (!PyObject_Length(ns)) { + /* nothing novel to pickle in od.__dict__ */ + Py_DECREF(ns); + ns = NULL; + } + } + + /* build the result */ + args = PyTuple_Pack(1, items); + if (args == NULL) + goto Done; + + cls = _PyObject_GetAttrId((PyObject *)od, &PyId___class__); + if (cls == NULL) { + Py_DECREF(args); + goto Done; + } + + if (ns == NULL) + result = PyTuple_Pack(2, cls, args); + else + result = PyTuple_Pack(3, cls, args, ns); + Py_DECREF(args); + Py_DECREF(cls); + +Done: + Py_DECREF(items); + Py_XDECREF(vars); + Py_XDECREF(ns); + + return result; +} + +/* setdefault() */ + +PyDoc_STRVAR(odict_setdefault__doc__, + "od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od"); + +static PyObject * +odict_setdefault(register PyODictObject *od, PyObject *args) +{ + PyObject *key, *result = NULL; + PyObject *failobj = Py_None; + + if (!PyArg_UnpackTuple(args, "setdefault", 1, 2, &key, &failobj)) + return NULL; + + /* XXX not subclass-friendly */ + if (_odict_find_node(od, key) != NULL) { + /* found it! */ + Py_DECREF(failobj); + /* XXX not subclass-friendly */ + result = PyODict_GetItem(od, key); /* borrowed reference */ + Py_INCREF(result); + /* XXX not subclass-friendly */ + } else if (PyODict_SetItem((PyObject *)od, key, failobj) >= 0) { + result = failobj; + } else { + Py_DECREF(failobj); + } + + Py_DECREF(key); + return result; +} + +/* pop() */ + +PyDoc_STRVAR(odict_pop__doc__, +"od.pop(k[,d]) -> v, remove specified key and return the corresponding\n\ + value. If key is not found, d is returned if given, otherwise KeyError\n\ + is raised.\n\ +\n\ + "); + +static PyObject * +odict_pop(PyODictObject *od, PyObject *args) +{ + PyObject *key = NULL, *value; + + /* XXX not subclass-friendly */ + value = dict_pop((PyDictObject *)od, args); + if (value != NULL) { + key = PyTuple_GET_ITEM(args, 0); /* borrowed reference */ + _odict_remove_key(od, key); + } + return value; +} + +/* popitem() */ + +PyDoc_STRVAR(odict_popitem__doc__, +"od.popitem() -> (k, v), return and remove a (key, value) pair.\n\ + Pairs are returned in LIFO order if last is true or FIFO order if false.\n\ +\n\ + "); + +static PyObject * +odict_popitem(PyODictObject *od, PyObject *args) +{ + PyObject *value, *item = NULL, *last = NULL; + _ODictNode *node; + + if (od->od_keys == NULL) { + PyErr_SetString(PyExc_KeyError, "dictionary is empty"); + return NULL; + } + + /* pull the item */ + + if (!PyArg_UnpackTuple(args, "popitem", 0, 1, &last)) + return NULL; + + if (last == NULL || last == Py_True) + node = od->od_last; + else + node = od->od_keys; + Py_XDECREF(last); + + /* XXX not subclass-friendly */ + value = PyODict_GetItem((PyObject *)od, node->key); /* borrowed */ + if (value == NULL) + return NULL; + + item = PyTuple_Pack(2, node->key, value); + if (item == NULL) + return NULL; + + /* remove it from the mapping */ + + /* XXX not subclass-friendly */ + _odict_remove_node(od, node, 1); + + /* XXX not subclass-friendly */ + if (PyDict_DelItem((PyObject *)od, node->key) < 0) { + Py_DECREF(item); + return NULL; + } + + return item; +} + +/* keys() */ + +/* MutableMapping.keys() does not have a docstring. */ +PyDoc_STRVAR(odict_keys__doc__, ""); + +static PyObject * odictkeys_new(PyObject *od); /* forward */ + +/* values() */ + +/* MutableMapping.values() does not have a docstring. */ +PyDoc_STRVAR(odict_values__doc__, ""); + +static PyObject * odictvalues_new(PyObject *od); /* forward */ + +/* items() */ + +/* MutableMapping.items() does not have a docstring. */ +PyDoc_STRVAR(odict_items__doc__, ""); + +static PyObject * odictitems_new(PyObject *od); /* forward */ + +/* update() */ + +/* MutableMapping.update() does not have a docstring. */ +PyDoc_STRVAR(odict_update__doc__, ""); + +/* forward */ +static PyObject * mutablemapping_update(PyObject *, PyObject *, PyObject *); + +#define odict_update mutablemapping_update + +/* clear() */ + +PyDoc_STRVAR(odict_clear__doc__, + "od.clear() -> None. Remove all items from od."); + +static PyObject * +odict_clear(register PyODictObject *od) +{ + /* XXX not subclass-friendly */ + if (dict_clear((PyDictObject *)od) == NULL) + return NULL; + _odict_clear_nodes(od); + Py_RETURN_NONE; +} + +/* copy() */ + +PyDoc_STRVAR(odict_copy__doc__, "od.copy() -> a shallow copy of od"); + +static PyObject * +odict_copy(register PyODictObject *od) +{ + _ODictNode *node; + PyObject *od_copy; + + /* XXX not subclass-friendly */ + od_copy = PyODict_New(); + if (od_copy == NULL) + return NULL; + + for (node = od->od_keys; node != NULL; node = node->next) { + /* XXX not subclass-friendly */ + PyObject *value = PyODict_GetItem(od, node->key); /* borrowed */ + if (value == NULL) { + Py_DECREF(od_copy); + return NULL; + } + /* XXX not subclass-friendly */ + if (PyODict_SetItem((PyObject *)od_copy, node->key, value) != 0) { + Py_DECREF(od_copy); + return NULL; + } + } + return od_copy; +} + +/* __reversed__() */ + +PyDoc_STRVAR(odict_reversed__doc__, "od.__reversed__() <==> reversed(od)"); + +static PyObject * +odict_reversed(register PyODictObject *od) +{ + _ODictNode *node; + Py_ssize_t index = 0; + PyObject *reversed, *iterator; + + reversed = PyList_New(PyODict_SIZE(od)); + if (reversed == NULL) + return NULL; + /* XXX not subclass-friendly */ + for (node = od->od_last; node != NULL; node = node->prev) { + Py_INCREF(node->key); + if (PyList_SetItem(reversed, index++, node->key) == -1) { /* steals */ + Py_DECREF(reversed); + return NULL; + } + } + iterator = PyObject_GetIter(reversed); + Py_DECREF(reversed); + return iterator; +} + +/* move_to_end() */ + +PyDoc_STRVAR(odict_move_to_end__doc__, +"Move an existing element to the end (or beginning if last==False).\n\ +\n\ + Raises KeyError if the element does not exist.\n\ + When last=True, acts like a fast version of self[key]=self.pop(key).\n\ +\n\ + "); + +static PyObject * +odict_move_to_end(PyODictObject *od, PyObject *args) +{ + PyObject *key, *last = NULL; + Py_ssize_t pos = -1; + + if (!PyArg_UnpackTuple(args, "move_to_end", 1, 2, &key, &last)) + return NULL; + if (last != NULL) { + Py_INCREF(last); + pos = PyObject_IsTrue(last) ? -1 : 0; + } + Py_INCREF(key); + if (_odict_move_key(od, key, pos) < 0) + return NULL; + + Py_DECREF(key); + Py_XDECREF(last); + Py_RETURN_NONE; +} + + +/* tp_methods */ + +static PyMethodDef odict_methods[] = { + + /* explicitly defined so we can align docstrings with + * collections.OrderedDict */ + {"__delitem__", (PyCFunction)odict_mp_ass_sub, METH_NOARGS, + odict_delitem__doc__}, + {"__eq__", (PyCFunction)odict_eq, METH_NOARGS, + odict_eq__doc__}, + {"__init__", (PyCFunction)odict_init, METH_NOARGS, + odict_init__doc__}, + {"__iter__", (PyCFunction)odict_iter, METH_NOARGS, + odict_iter__doc__}, + {"__ne__", (PyCFunction)odict_ne, METH_NOARGS, + odict_ne__doc__}, + {"__repr__", (PyCFunction)odict_repr, METH_NOARGS, + odict_repr__doc__}, + {"__setitem__", (PyCFunction)odict_mp_ass_sub, METH_NOARGS, + odict_setitem__doc__}, + {"fromkeys", (PyCFunction)dict_fromkeys, METH_VARARGS | METH_CLASS, + odict_fromkeys__doc__}, + + /* overridden dict methods */ + {"__sizeof__", (PyCFunction)odict_sizeof, METH_NOARGS, + odict_sizeof__doc__}, + {"__reduce__", (PyCFunction)odict_reduce, METH_NOARGS, + odict_reduce__doc__}, + {"setdefault", (PyCFunction)odict_setdefault, METH_VARARGS, + odict_setdefault__doc__}, + {"pop", (PyCFunction)odict_pop, METH_VARARGS, + odict_pop__doc__}, + {"popitem", (PyCFunction)odict_popitem, METH_VARARGS, + odict_popitem__doc__}, + {"keys", (PyCFunction)odictkeys_new, METH_NOARGS, + odict_keys__doc__}, + {"values", (PyCFunction)odictvalues_new, METH_NOARGS, + odict_values__doc__}, + {"items", (PyCFunction)odictitems_new, METH_NOARGS, + odict_items__doc__}, + {"update", (PyCFunction)odict_update, METH_VARARGS | METH_KEYWORDS, + odict_update__doc__}, + {"clear", (PyCFunction)odict_clear, METH_NOARGS, + odict_clear__doc__}, + {"copy", (PyCFunction)odict_copy, METH_NOARGS, + odict_copy__doc__}, + + /* new methods */ + {"__reversed__", (PyCFunction)odict_reversed, METH_NOARGS, + odict_reversed__doc__}, + {"move_to_end", (PyCFunction)odict_move_to_end, METH_VARARGS, + odict_move_to_end__doc__}, + + {NULL, NULL} /* sentinel */ +}; + + +/* ---------------------------------------------- + * OrderedDict members + */ + +/* tp_members */ + +static PyMemberDef odict_members[] = { + {"__dict__", T_OBJECT, offsetof(PyODictObject, od_inst_dict), READONLY}, + {0} +}; + + +/* ---------------------------------------------- + * OrderedDict type slot methods + */ + +/* tp_dealloc */ + +static void +odict_dealloc(PyODictObject *self) +{ + /* XXX not subclass-friendly */ + PyDict_Type.tp_dealloc((PyObject *)self); + Py_XDECREF(self->od_inst_dict); + if (self->od_weakreflist != NULL) + PyObject_ClearWeakRefs((PyObject *)self); + _odict_clear_nodes(self); +}; + +/* tp_repr */ + +static PyObject * +odict_repr(PyODictObject *self) +{ + int i; + const char *formatstr, *name; + Py_ssize_t count = -1; + PyObject *pieces = NULL, *result = NULL; + PyObject *classname = NULL, *format = NULL, *args = NULL; + _ODictNode *node; + + i = Py_ReprEnter((PyObject *)self); + if (i != 0) { + return i > 0 ? PyUnicode_FromString("...") : NULL; + } + + if (PyODict_SIZE(self) == 0) { + /* "OrderedDict()" */ + goto Finish; + } + + pieces = PyList_New(PyODict_SIZE(self)); + if (pieces == NULL) + goto Done; + + for (node = self->od_keys; node != NULL; node = node->next) { + PyObject *pair, *value; + + value = PyODict_GetItem(self, node->key); /* borrowed reference */ + + if (value == NULL) + goto Done; + pair = PyTuple_Pack(2, node->key, value); + if (pair == NULL) + goto Done; + + PyList_SET_ITEM(pieces, ++count, pair); /* steals reference */ + } + +Finish: + /* XXX drop the module name */ + name = strrchr(Py_TYPE(self)->tp_name, '.'); + if (name == NULL) + name = Py_TYPE(self)->tp_name; + else + name++; + classname = PyUnicode_FromString(name); + if (classname == NULL) + goto Done; + + if (pieces == NULL) { + formatstr = "%s()"; + args = PyTuple_Pack(1, classname); + } + else { + formatstr = "%s(%r)"; + args = PyTuple_Pack(2, classname, pieces); + } + if (args == NULL) + goto Done; + + format = PyUnicode_InternFromString(formatstr); + if (format == NULL) + goto Done; + + result = PyUnicode_Format(format, args); +Done: + Py_XDECREF(pieces); + Py_XDECREF(classname); + Py_XDECREF(format); + Py_XDECREF(args); + Py_ReprLeave((PyObject *)self); + return result; +}; + +/* tp_doc */ + +PyDoc_STRVAR(odict_doc, + "Dictionary that remembers insertion order"); + +/* tp_traverse */ + +static int +odict_traverse(PyODictObject *od, visitproc visit, void *arg) +{ + Py_VISIT(od->od_inst_dict); + Py_VISIT(od->od_weakreflist); + return dict_traverse((PyObject *)od, visit, arg); +} + +/* tp_clear */ + +static int +odict_tp_clear(PyODictObject *od) +{ + Py_CLEAR(od->od_inst_dict); + Py_CLEAR(od->od_weakreflist); + return dict_tp_clear((PyObject *)od); +} + +/* tp_richcompare */ + +static PyObject * +odict_richcompare(PyObject *v, PyObject *w, int op) +{ + /* XXX allow for any mapping type... */ + if (!PyODict_Check(v) || !PyDict_Check(w)) { + Py_RETURN_NOTIMPLEMENTED; + } + else if (op == Py_EQ || op == Py_NE) { + PyObject *res, *cmp; + int eq; + + /* XXX not subclass-friendly */ + cmp = PyDict_Type.tp_richcompare(v, w, op); + + if (cmp == NULL) + eq = -1; + else if (cmp == Py_False) + eq = 0; + else if (!PyODict_Check(w)) + eq = 1; + else { + /* Try comparing odict keys. */ + /* XXX not subclass-friendly */ + eq = _odict_keys_equal((PyODictObject *)v, (PyODictObject *)w); + } + Py_DECREF(cmp); + + if (eq < 0) + res = NULL; + else { + res = (eq == (op == Py_EQ)) ? Py_True : Py_False; + Py_INCREF(res); + } + return res; + } + else { + Py_RETURN_NOTIMPLEMENTED; + } +}; + +/* tp_iter */ + +static PyObject * +odict_iter(PyODictObject *od) +{ + /* XXX cheating */ + _ODictNode *node; + PyObject *keys = PyList_New(0); + + for (node = ((PyODictObject *)od)->od_keys; node != NULL; node = node->next) { + PyList_Append(keys, node->key); + } + return PyObject_GetIter(keys); +}; + +/* tp_init */ + +static int +odict_init(PyObject *self, PyObject *args, PyObject *kwds) +{ + Py_ssize_t len = PyObject_Length(args); + + if (len == -1) + return -1; + if (len > 1) { + char *msg = "expected at most 1 arguments, got %d"; + PyErr_Format(PyExc_TypeError, msg, len); + return -1; + } + + if (odict_update(self, args, kwds) == NULL) + return -1; + else + return 0; +}; + +/* tp_new */ + +static PyObject * +odict_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyObject *od = dict_new(type, args, kwds); + ((PyODictObject *)od)->od_inst_dict = PyDict_New(); + ((PyODictObject *)od)->od_weakreflist = NULL; + ((PyODictObject *)od)->od_keys = NULL; + ((PyODictObject *)od)->od_last = NULL; + return od; +} + +/* PyODict_Type */ + +/* XXX make sure everything is at a thread-safe state when calling into + * Python... + */ + +PyTypeObject PyODict_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + "collections.OrderedDict", /* tp_name */ + sizeof(PyODictObject), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)odict_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + (reprfunc)odict_repr, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + &odict_as_mapping, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,/* tp_flags */ + odict_doc, /* tp_doc */ + (traverseproc)odict_traverse, /* tp_traverse */ + (inquiry)odict_tp_clear, /* tp_clear */ + (richcmpfunc)odict_richcompare, /* tp_richcompare */ + offsetof(PyODictObject, od_weakreflist), /* tp_weaklistoffset */ + (getiterfunc)odict_iter, /* tp_iter */ + 0, /* tp_iternext */ + odict_methods, /* tp_methods */ + odict_members, /* tp_members */ + 0, /* tp_getset */ + &PyDict_Type, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + offsetof(PyODictObject, od_inst_dict), /* tp_dictoffset */ + (initproc)odict_init, /* tp_init */ + 0, /* tp_alloc */ + (newfunc)odict_new, /* tp_new */ + 0, /* tp_free */ +}; + + +/* ---------------------------------------------- + * the public OrderedDict API + */ + +/* PyODict_New is intended as a more efficient approach than odict_new. */ +PyObject * +PyODict_New(void) { + PyODictObject *od; + + /* XXX do the whole free_list thing, a la PyDict_New? */ + od = PyObject_GC_New(PyODictObject, &PyODict_Type); + if (od == NULL) + return NULL; + ((PyDictObject *)od)->ma_keys = new_keys_object(PyDict_MINSIZE_COMBINED); + ((PyDictObject *)od)->ma_values = NULL; + ((PyDictObject *)od)->ma_used = 0; + od->od_inst_dict = PyDict_New(); + od->od_weakreflist = NULL; + od->od_keys = NULL; + od->od_last = NULL; + return (PyObject *)od; +}; + +int +PyODict_SetItem(PyObject *od, PyObject *key, PyObject *item) { + /* XXX not subclass-friendly */ + int res = PyDict_SetItem(od, key, item); + if (res == 0) { + /* XXX not subclass-friendly */ + res = _odict_add_key((PyODictObject *)od, key); + if (res != 0) + PyErr_SetNone(PyExc_MemoryError); + } + return res; +}; + +int +PyODict_DelItem(PyObject *od, PyObject *key) { + /* XXX not subclass-friendly */ + int res = PyDict_DelItem(od, key); + if (res == 0) + /* XXX not subclass-friendly */ + _odict_remove_key((PyODictObject *)od, key); + return res; +}; + + +/* ------------------------------------------- + * The OrderedDict views (keys/values/items) + */ + +/* keys() */ + +static PyObject * +odictkeys_new(PyObject *od) +{ + /* XXX cheating */ + _ODictNode *node; + PyObject *keys = PyList_New(0); + + for (node = ((PyODictObject *)od)->od_keys; node != NULL; node = node->next) { + PyList_Append(keys, node->key); + } + return PyObject_GetIter(keys); +} + +/* items() */ + +static PyObject * +odictitems_new(PyObject *od) +{ + /* XXX cheating */ + _ODictNode *node; + PyObject *items = PyList_New(0); + + for (node = ((PyODictObject *)od)->od_keys; node != NULL; node = node->next) { + PyList_Append(items, + PyTuple_Pack(2, node->key, PyODict_GetItem(od, node->key))); + } + return PyObject_GetIter(items); +} + +/* values() */ + +static PyObject * +odictvalues_new(PyObject *od) +{ + /* XXX cheating */ + _ODictNode *node; + PyObject *value, *values = PyList_New(0); + + for (node = ((PyODictObject *)od)->od_keys; node != NULL; node = node->next) { + value = PyODict_GetItem(od, node->key); + Py_INCREF(value); + PyList_Append(values, value); + } + return PyObject_GetIter(values); +} + + +/* ---------------------------------------------- + * MutableMappping implementations + */ + +static int +mutablemapping_add_pairs(PyObject *self, PyObject *pairs) +{ + PyObject *pair, *iterator; + int res = 0; + + iterator = PyObject_GetIter(pairs); + if (iterator == NULL) + return -1; + PyErr_Clear(); + + while ((pair = PyIter_Next(iterator)) != NULL) { + /* XXX could be more efficient (see UNPACK_SEQUENCE in ceval.c) */ + PyObject * key, *value = NULL, *pair_iterator = PyObject_GetIter(pair); + + key = PyIter_Next(pair_iterator); + if (key == NULL) { + if (!PyErr_Occurred()) + PyErr_SetString(PyExc_ValueError, + "need more than 0 values to unpack"); + goto Done; + } + + value = PyIter_Next(pair_iterator); + if (value == NULL) { + if (!PyErr_Occurred()) + PyErr_SetString(PyExc_ValueError, + "need more than 1 value to unpack"); + goto Done; + } + + if (PyIter_Next(pair_iterator) != NULL) { + PyErr_SetString(PyExc_ValueError, + "too many values to unpack (expected 2)"); + goto Done; + } + else if (PyErr_Occurred()) + goto Done; + + res = PyObject_SetItem(self, key, value); + +Done: + Py_DECREF(pair); + Py_DECREF(pair_iterator); + Py_XDECREF(key); + Py_XDECREF(value); + if (PyErr_Occurred()) + break; + } + Py_DECREF(iterator); + + if (res < 0 || PyErr_Occurred() != NULL) + return -1; + else + return 0; +} + +static PyObject * +mutablemapping_update(PyObject *self, PyObject *args, PyObject *kwargs) +{ + int res = 0; + Py_ssize_t len = (args != NULL) ? PyObject_Size(args) : 0; + + /* first handle args, if any */ + if (len < 0) + return NULL; + else if (len > 1) { + char *msg = "update() takes at most 1 positional argument (%d given)"; + PyErr_Format(PyExc_TypeError, msg, len); + return NULL; + } + else if (len == 1) { + PyObject *other = PyTuple_GET_ITEM(args, 0); /* borrowed reference */ + if (other == NULL) + return NULL; + Py_INCREF(other); +if (Py_TYPE(other) != &PyODict_Type) { +PyErr_SetObject(PyExc_RuntimeError, PyTuple_Pack(2, self, other)); +return NULL; +} + if (PyObject_HasAttrString(other, "items")) { + PyObject *items = PyMapping_Items(other); + Py_DECREF(other); + if (items == NULL) + return NULL; + res = mutablemapping_add_pairs(self, items); + Py_DECREF(items); + if (res == -1) + return NULL; + } + else if (PyObject_HasAttrString(other, "keys")) { + PyObject *keys, *iterator, *key; + keys = PyObject_CallMethod(other, "keys", NULL); + Py_DECREF(other); + iterator = PyObject_GetIter(keys); + Py_XDECREF(keys); + if (iterator == NULL) + return NULL; + while ( (key = PyIter_Next(iterator)) ) { + PyObject *value = PyObject_GetItem(other, key); + res = PyObject_SetItem(self, key, value); + Py_DECREF(key); + Py_DECREF(value); + if (res == -1) + break; + } + Py_DECREF(iterator); + if (res == -1) + return NULL; + } + else { + res = mutablemapping_add_pairs(self, other); + Py_DECREF(other); + if (res != 0) + return NULL; + } + } + + /* now handle kwargs */ + len = (kwargs != NULL) ? PyObject_Size(kwargs) : 0; + if (len < 0) + return NULL; + else if (len > 0) { + PyObject *items; + if (!PyMapping_Check(kwargs)) { + PyErr_SetString(PyExc_TypeError, "expected mapping for kwargs"); + return NULL; + } + items = PyMapping_Items(kwargs); + if (items == NULL) + return NULL; + res = mutablemapping_add_pairs(self, items); + Py_DECREF(items); + if (res == -1) + return NULL; + } + +if (PyObject_Size(args) == 1) { +PyErr_SetObject(PyExc_RuntimeError, PyTuple_Pack(2, self, args)); +return NULL; +} + Py_RETURN_NONE; +} diff --git a/Objects/object.c b/Objects/object.c --- a/Objects/object.c +++ b/Objects/object.c @@ -1627,6 +1627,9 @@ if (PyType_Ready(&PyDict_Type) < 0) Py_FatalError("Can't initialize dict type"); + if (PyType_Ready(&PyODict_Type) < 0) + Py_FatalError("Can't initialize OrderedDict type"); + if (PyType_Ready(&PySet_Type) < 0) Py_FatalError("Can't initialize set type");