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,39 @@ PyAPI_FUNC(void) _PyDict_DebugMallocStats(FILE *out); #endif +/* OrderedDict */ + +#ifndef Py_LIMITED_API + +typedef struct _odictobject PyODictObject; + +PyAPI_DATA(PyTypeObject) PyODict_Type; +PyAPI_DATA(PyTypeObject) PyODictIterKey_Type; +PyAPI_DATA(PyTypeObject) PyODictIterItem_Type; +PyAPI_DATA(PyTypeObject) PyODictIterValue_Type; +PyAPI_DATA(PyTypeObject) PyODictKeys_Type; +PyAPI_DATA(PyTypeObject) PyODictItems_Type; +PyAPI_DATA(PyTypeObject) PyODictValues_Type; + +#endif /* Py_LIMITED_API */ + +#define PyODict_Check(op) \ + PyType_IsSubtype(((PyObject *)op)->ob_type, &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 @@ -232,6 +232,12 @@ return dict.__eq__(self, other) +try: + from builtins import _odict as OrderedDict +except ImportError: + pass + + ################################################################################ ### namedtuple ################################################################################ diff --git a/Objects/dictobject.c b/Objects/dictobject.c --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -3794,3 +3794,1461 @@ 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 _odictkey _ODictKey; + +/* PyODictObject */ +struct _odictobject { + PyDictObject dict; + _ODictKey *keys; + _ODictKey *last; +}; + +#define _PyODict_BASE(op) ((PyTypeObject*)(Py_TYPE(op)->tp_base)) +#define _PyODict_BASEMETHOD(op, name) \ + (PyObject_GetAttrString((PyObject *)_PyODict_BASE(od), name)) + + +/* ---------------------------------------------- + * odict keys (a simple doubly-linked list) + */ + +struct _odictkey { + PyObject *key; + _ODictKey *next; + _ODictKey *prev; +}; + +static _ODictKey * +_odict_get_key(PyODictObject *od, PyObject *key) +{ + _ODictKey *node; + for (node = od->keys; node != NULL; node = node->next) + if (PyObject_RichCompareBool(key, node->key, Py_EQ)) + return node; + return NULL; +} + +static void +_odict_add_key_start(PyODictObject *od, PyObject *key) +{ + _ODictKey *node = (_ODictKey *)PyMem_Malloc(sizeof(_ODictKey)); + /* XXX check for out-of-memory? */ + + node->key = key; + node->prev = NULL; + node->next = od->keys; + if (od->last == NULL) + od->last = node; + else + od->keys->prev = node; + od->keys = node; +} + +static void +_odict_add_key_end(PyODictObject *od, PyObject *key) +{ + _ODictKey *node = (_ODictKey *)PyMem_Malloc(sizeof(_ODictKey)); + /* XXX check for out-of-memory? */ + + node->key = key; + node->prev = od->last; + node->next = NULL; + if (od->keys == NULL) + od->keys = node; + else + od->last->next = node; + od->last = node; +} + +#define _odict_add_key(od, key) _odict_add_key_end(od, key) + +static void +_odict_remove_key(PyODictObject *od, PyObject *key) +{ + _ODictKey *node = _odict_get_key(od, key); + if (node == NULL) + return; + if (node->prev != NULL) + node->prev->next = node->next; + if (node->next != NULL) + node->next->prev = node->prev; + Py_DECREF(node->key); + PyMem_Free((void *)node); +} + +static void +_odict_clear_keys(PyODictObject *od) +{ + _ODictKey *node; + for (node = od->keys; node != NULL; node = node->next) { + Py_DECREF(node->key); + if (node->prev != NULL) + PyMem_Free((void *)(node->prev)); + } + if (od->last != NULL) + PyMem_Free((void *)(od->last)); + od->last = NULL; + od->keys = NULL; +} + +static int +_odict_keys_equal(PyODictObject *a, PyODictObject *b) +{ + _ODictKey *node_a, *node_b; + + node_a = a->keys; + node_b = b->keys; + while (1) { + if (node_a == NULL && node_b == NULL) + return 1; + else if (node_a == NULL || node_b == NULL) + /* unequal length */ + return 0; + else if (!PyObject_RichCompareBool((PyObject *)a, (PyObject *)b, Py_EQ)) + 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->keys != NULL) { + /* XXX not subclass-friendly */ + res += sizeof(_ODictKey) * ((PyDictObject *)od)->ma_used; + } + 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; + _ODictKey *node; + Py_ssize_t i = 0; + + items = PyList_New(PyODict_SIZE(od)); + if (items == NULL) + return NULL; + + for (node = od->keys; node != NULL; node = node->next) { + value = PyODict_GetItem(od, node->key); + if (value == NULL) + goto Done; + pair = PyList_New(2); + if (pair == NULL) + goto Done; + if (!PyList_Insert(pair, 0, node->key)) + goto Done; + if (!PyList_Insert(pair, 1, value)) + goto Done; + Py_DECREF(value); + if (!PyList_Insert(items, i, pair)) + goto Done; + Py_DECREF(pair); + ++i; + } + + /* capture any instance state */ + vars = _PyObject_GetAttrId((PyObject *)od, &PyId___dict__); + if (vars != NULL) { + PyObject *od_vars, *iterator, *key; + + ns = PyObject_CallMethod((PyObject *)od, "copy", NULL); + if (ns == NULL) + goto Done; + od_vars = _PyObject_GetAttrId((PyObject *)&PyODict_Type, + &PyId___dict__); + 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)) ) { + if (PyMapping_HasKey(ns, key) && PyMapping_DelItem(ns, key) == -1) { + Py_DECREF(iterator); + Py_DECREF(key); + goto Done; + } + Py_DECREF(key); + } + Py_DECREF(iterator); + if (!PyObject_Length(ns)) { + 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(args); + Py_XDECREF(pair); + Py_XDECREF(value); + 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_get_key(od, key) == NULL) { + Py_DECREF(failobj); + /* XXX not subclass-friendly */ + result = PyODict_GetItem(od, key); + /* 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); + _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 *item; + + /* XXX not subclass-friendly */ + item = dict_popitem((PyDictObject *)od); + if (item != NULL) { + PyObject *key = PyTuple_GET_ITEM(item, 0); + _odict_remove_key(od, key); + } + 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_keys(od); + Py_RETURN_NONE; +} + +/* copy() */ + +PyDoc_STRVAR(odict_copy__doc__, "od.copy() -> a shallow copy of od"); + +static PyObject * +odict_copy(register PyODictObject *od) +{ + _ODictKey *node; + /* XXX not subclass-friendly */ + PyObject *od_copy; + + od_copy = PyODict_New(); + if (od_copy == NULL) + return NULL; + + for (node = od->keys; node != NULL; node = node->next) { + /* XXX not subclass-friendly */ + PyObject *value = PyODict_GetItem(od, node->key); + if (value == NULL) { + Py_DECREF(od_copy); + return NULL; + } + Py_INCREF(node->key); + /* XXX not subclass-friendly */ + if (PyODict_SetItem((PyObject *)od, node->key, value) == -1) { + Py_DECREF(node->key); + Py_DECREF(value); + 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) +{ + _ODictKey *node; + Py_ssize_t index = 0; + PyObject *reversed; + + reversed = PyList_New(PyODict_SIZE(od)); + if (reversed == NULL) + return NULL; + /* XXX not subclass-friendly */ + for (node = od->last; node != NULL; node = node->prev) { + Py_INCREF(node->key); + if (PyList_SetItem(reversed, index++, node->key) == -1) { + Py_DECREF(node->key); + Py_DECREF(reversed); + return NULL; + } + } + return PyObject_GetIter(reversed); +} + +/* 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; + if (!PyArg_UnpackTuple(args, "move_to_end", 1, 2, &key, &last)) + return NULL; + _odict_remove_key(od, key); + if (PyObject_IsTrue(last)) { + _odict_add_key_end(od, key); + } else { + _odict_add_key_start(od, key); + } + 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_NOARGS, + 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_NOARGS, + 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 type slot methods + */ + +/* tp_dealloc */ + +static void +odict_dealloc(PyODictObject *self) +{ + _odict_clear_keys(self); + /* XXX not subclass-friendly */ + PyDict_Type.tp_dealloc((PyObject *)self); +}; + +/* tp_repr */ + +static PyObject * +odict_repr(PyODictObject *self) +{ + int i; + const char *formatstr; + Py_ssize_t count = -1; + PyObject *pieces = NULL, *result = NULL; + PyObject *classname = NULL, *format = NULL, *args = NULL; + _ODictKey *node; + + i = Py_ReprEnter((PyObject *)self); + if (i != 0) { + return i > 0 ? PyUnicode_FromString("...") : NULL; + } + + if (PyODict_SIZE(self) == 0) { + /* return "OrderedDict()" */ + goto Finish; + } + + pieces = PyList_New(PyODict_SIZE(self)); + if (pieces == NULL) + goto Done; + + for (node = self->keys; node != NULL; node = node->next) { + PyObject *pair, *value = PyODict_GetItem(self, node->key); + + if (value == NULL) + goto Done; + pair = PyTuple_Pack(2, node->key, value); + Py_DECREF(value); + if (pair == NULL) + goto Done; + + PyList_SET_ITEM(pieces, ++count, pair); + } + +Finish: + classname = PyUnicode_FromString(Py_TYPE(self)->tp_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_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; + + /* XXX not subclass-friendly */ + cmp = PyDict_Type.tp_richcompare(v, w, op); + + if (cmp == NULL) + return NULL; + else if (cmp == Py_False) + res = Py_False; + else if (!PyODict_Check(w)) + res = Py_True; + else { + /* Try comparing odict keys. */ + /* XXX not subclass-friendly */ + int eq = _odict_keys_equal((PyODictObject *)v, (PyODictObject *)w); + + if (eq < 0) + return NULL; + res = (eq == (op == Py_EQ)) ? Py_True : Py_False; + } + Py_INCREF(res); + return res; + } + else { + Py_RETURN_NOTIMPLEMENTED; + } +}; + +/* tp_iter */ + +static PyObject * odictiter_new(PyODictObject *, PyTypeObject *); /* future */ + +static PyObject * +odict_iter(PyODictObject *od) +{ + return odictiter_new(od, &PyODictIterKey_Type); +}; + +/* 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; + } + + /* XXX not subclass-friendly */ + if (odict_update(self, args, kwds) == NULL) + return -1; + else + return 0; +}; + +/* tp_new */ + +static PyObject * +odict_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + return PyODict_New(); +} + +/* PyODict_Type */ + +PyTypeObject PyODict_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + "OrderedDict", /* tp_name */ + sizeof(PyODictObject), /* tp_basicsize */ /* XXX different? */ + 0, /* tp_itemsize */ /* XXX different? */ + (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_HAVE_GC | + Py_TPFLAGS_BASETYPE | Py_TPFLAGS_DICT_SUBCLASS, /* tp_flags */ + odict_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + odict_richcompare, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + (getiterfunc)odict_iter, /* tp_iter */ + 0, /* tp_iternext */ + odict_methods, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + &PyDict_Type, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + odict_init, /* tp_init */ + 0, /* tp_alloc */ + 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->keys = NULL; + 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 */ + _odict_add_key((PyODictObject *)od, key); + 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) + */ + +typedef struct { + PyObject_HEAD + PyODictObject *di_odict; + _ODictKey *di_current; + PyObject* di_result; /* reusable result tuple for iteritems */ +} odictiterobject; + +static PyObject * +odictiter_new(PyODictObject *od, PyTypeObject *itertype) +{ + odictiterobject *di; + + di = PyObject_GC_New(odictiterobject, itertype); + if (di == NULL) + return NULL; + + Py_INCREF(od); + di->di_odict = od; + di->di_current = od->keys; + if (itertype == &PyODictIterItem_Type) { + di->di_result = PyTuple_Pack(2, Py_None, Py_None); + if (di->di_result == NULL) { + Py_DECREF(di); + return NULL; + } + } + else + di->di_result = NULL; + _PyObject_GC_TRACK(di); + return (PyObject *)di; +} + +static void +odictiter_dealloc(odictiterobject *di) +{ + Py_XDECREF(di->di_odict); + Py_XDECREF(di->di_result); + PyObject_GC_Del(di); +} + +static int +odictiter_traverse(odictiterobject *di, visitproc visit, void *arg) +{ + Py_VISIT(di->di_odict); + Py_VISIT(di->di_result); + return 0; +} + +static PyObject * +odictiter_len(odictiterobject *di) +{ + Py_ssize_t len = 0; + _ODictKey *current = di->di_current; + + while (current != NULL) { + len += 1; + current = current->next; + } + return PyLong_FromSize_t(len); +} + +static PyObject * odictiter_iternextkey(odictiterobject *); /* forward */ +static PyObject * odictiter_iternextitem(odictiterobject *); /* forward */ +static PyObject * odictiter_iternextvalue(odictiterobject *); /* forward */ + +static PyObject * +odictiter_reduce(odictiterobject *di) +{ + PyObject *list; + odictiterobject tmp; + + list = PyList_New(0); + if (!list) + return NULL; + + /* copy the itertor state */ + tmp = *di; + Py_XINCREF(tmp.di_odict); + + /* iterate the temporary into a list */ + for(;;) { + PyObject *element = 0; + if (Py_TYPE(di) == &PyODictIterItem_Type) + element = odictiter_iternextitem(&tmp); + else if (Py_TYPE(di) == &PyODictIterKey_Type) + element = odictiter_iternextkey(&tmp); + else if (Py_TYPE(di) == &PyODictIterValue_Type) + element = odictiter_iternextvalue(&tmp); + else + assert(0); + if (element) { + if (PyList_Append(list, element)) { + Py_DECREF(element); + Py_DECREF(list); + Py_XDECREF(tmp.di_odict); + return NULL; + } + Py_DECREF(element); + } else + break; + } + Py_XDECREF(tmp.di_odict); + /* check for error */ + if (tmp.di_odict != NULL) { + /* we have an error */ + Py_DECREF(list); + return NULL; + } + return Py_BuildValue("N(N)", _PyObject_GetBuiltin("iter"), list); +} + +static PyMethodDef odictiter_methods[] = { + {"__length_hint__", (PyCFunction)odictiter_len, METH_NOARGS, + length_hint_doc}, + {"__reduce__", (PyCFunction)odictiter_reduce, METH_NOARGS, + reduce_doc}, + {NULL, NULL} /* sentinel */ +}; + +PyTypeObject PyODictIterView_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + "odict_viewiterator", /* tp_name */ + sizeof(odictiterobject), /* tp_basicsize */ + 0, /* tp_itemsize */ + /* methods */ + (destructor)odictiter_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* 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, /* tp_flags */ + 0, /* tp_doc */ + (traverseproc)odictiter_traverse, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + PyObject_SelfIter, /* tp_iter */ + 0, /* tp_iternext */ + odictiter_methods, /* tp_methods */ + 0, +}; + +/* keys() */ + +static PyObject * +odictiter_iternextkey(odictiterobject *di) +{ + /* XXX handle resized while iterating case? */ + PyObject *key; + + if (di->di_current == NULL) + return NULL; + + key = di->di_current->key; + di->di_current = di->di_current->next; + return key; +} + +PyTypeObject PyODictIterKey_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + "odict_keyiterator", /* tp_name */ + 0, /* tp_basicsize */ + 0, /* tp_itemsize */ + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* 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_HAVE_GC,/* tp_flags */ + 0, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + (iternextfunc)odictiter_iternextkey, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + &PyODictIterView_Type, /* tp_base */ + 0, +}; + +static PyObject * +odictkeys_iter(dictviewobject *dv) +{ + if (dv->dv_dict == NULL) { + Py_RETURN_NONE; + } + return odictiter_new((PyODictObject *)dv->dv_dict, &PyODictIterKey_Type); +} + +PyTypeObject PyODictKeys_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + "odict_keys", /* tp_name */ + 0, /* tp_basicsize */ + 0, /* tp_itemsize */ + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* 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_HAVE_GC,/* tp_flags */ + 0, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + (getiterfunc)odictkeys_iter, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + &PyDictKeys_Type, /* tp_base */ +}; + +static PyObject * +odictkeys_new(PyObject *od) +{ + return dictview_new(od, &PyODictKeys_Type); +} + +/* items() */ + +static PyObject * +odictiter_iternextitem(odictiterobject *di) +{ + /* XXX handle resized while iterating case? */ + PyObject *key, *value; + + if (di->di_current == NULL) + return NULL; + + key = di->di_current->key; + di->di_current = di->di_current->next; + + value = PyODict_GetItem(di->di_odict, key); + if (value == NULL) + return NULL; + return PyTuple_Pack(2, key, value); +} + +PyTypeObject PyODictIterItem_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + "odict_itemiterator", /* tp_name */ + 0, /* tp_basicsize */ + 0, /* tp_itemsize */ + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* 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_HAVE_GC,/* tp_flags */ + 0, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + (iternextfunc)odictiter_iternextitem, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + &PyODictIterView_Type, /* tp_base */ + 0, +}; + +static PyObject * +odictitems_iter(dictviewobject *dv) +{ + if (dv->dv_dict == NULL) { + Py_RETURN_NONE; + } + return odictiter_new((PyODictObject *)dv->dv_dict, &PyODictIterItem_Type); +} + +PyTypeObject PyODictItems_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + "odict_items", /* tp_name */ + 0, /* tp_basicsize */ + 0, /* tp_itemsize */ + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* 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_HAVE_GC,/* tp_flags */ + 0, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + (getiterfunc)odictitems_iter, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + &PyDictItems_Type, /* tp_base */ +}; + +static PyObject * +odictitems_new(PyObject *od) +{ + return dictview_new(od, &PyODictItems_Type); +} + +/* values() */ + +static PyObject * +odictiter_iternextvalue(odictiterobject *di) +{ + /* XXX handle resized while iterating case? */ + PyObject *key; + + if (di->di_current == NULL) + return NULL; + + key = di->di_current->key; + di->di_current = di->di_current->next; + return PyODict_GetItem(di->di_odict, key); +} + +PyTypeObject PyODictIterValue_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + "odict_valueiterator", /* tp_name */ + 0, /* tp_basicsize */ + 0, /* tp_itemsize */ + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* 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_HAVE_GC,/* tp_flags */ + 0, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + (iternextfunc)odictiter_iternextvalue, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + &PyODictIterView_Type, /* tp_base */ + 0, +}; + +static PyObject * +odictvalues_iter(dictviewobject *dv) +{ + if (dv->dv_dict == NULL) { + Py_RETURN_NONE; + } + return odictiter_new((PyODictObject *)dv->dv_dict, &PyODictIterValue_Type); +} + +PyTypeObject PyODictValues_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + "odict_values", /* tp_name */ + 0, /* tp_basicsize */ + 0, /* tp_itemsize */ + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* 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_HAVE_GC,/* tp_flags */ + 0, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + (getiterfunc)odictvalues_iter, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + &PyDictValues_Type, /* tp_base */ +}; + +static PyObject * +odictvalues_new(PyObject *od) +{ + return dictview_new(od, &PyODictValues_Type); +} + + +/* ---------------------------------------------- + * 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; + + while ( (pair = PyIter_Next(iterator)) ) { + if (!PyTuple_Check(pair)) { + PyErr_SetString(PyExc_TypeError, "expected pair to be tuple"); + res = -1; + break; + } + else if (PyObject_Size(pair) != 2) { + char *msg = "expected tuple of size 2"; + PyErr_SetString(PyExc_ValueError, msg); + res = -1; + break; + } + else { + PyObject *key, *value; + + key = PyTuple_GET_ITEM(pair, 0); + if (key == NULL) { + res = -1; + break; + } + value = PyTuple_GET_ITEM(pair, 1); + if (value == NULL) { + res = -1; + break; + } + res = PyObject_SetItem(self, key, value); + if (res == -1) + break; + } + Py_DECREF(pair); + } + Py_DECREF(iterator); + + return res; +} + +static PyObject * +mutablemapping_update(PyObject *self, PyObject *args, PyObject *kwargs) +{ + int res = 0; + Py_ssize_t len = PyObject_Size(args); + + /* first handle args, if any */ + if (len == -1) { + 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); + if (PyMapping_Check(other)) { + PyObject *items = PyMapping_Items(kwargs); + 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_DECREF(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 == -1) + return NULL; + } + } + + /* now handle kwargs */ + if (kwargs != NULL && PyObject_Size(kwargs) > 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; + } + + Py_RETURN_NONE; +} diff --git a/Objects/object.c b/Objects/object.c --- a/Objects/object.c +++ b/Objects/object.c @@ -1627,6 +1627,27 @@ 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(&PyODictKeys_Type) < 0) + Py_FatalError("Can't initialize odict_keys type"); + + if (PyType_Ready(&PyODictItems_Type) < 0) + Py_FatalError("Can't initialize odict_items type"); + + if (PyType_Ready(&PyODictValues_Type) < 0) + Py_FatalError("Can't initialize odict_values type"); + + if (PyType_Ready(&PyODictIterKey_Type) < 0) + Py_FatalError("Can't initialize odict_keyiterator type"); + + if (PyType_Ready(&PyODictIterItem_Type) < 0) + Py_FatalError("Can't initialize odict_itemiterator type"); + + if (PyType_Ready(&PyODictIterValue_Type) < 0) + Py_FatalError("Can't initialize odict_valueiterator type"); + if (PyType_Ready(&PySet_Type) < 0) Py_FatalError("Can't initialize set type"); diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -2433,6 +2433,7 @@ return NULL; \ ADD_TO_ALL(OBJECT) + SETBUILTIN("_odict", &PyODict_Type); SETBUILTIN("None", Py_None); SETBUILTIN("Ellipsis", Py_Ellipsis); SETBUILTIN("NotImplemented", Py_NotImplemented);