Index: Include/dictobject.h =================================================================== --- Include/dictobject.h (revision 60465) +++ Include/dictobject.h (working copy) @@ -89,10 +89,22 @@ }; PyAPI_DATA(PyTypeObject) PyDict_Type; +PyAPI_DATA(PyTypeObject) PyDictIterKey_Type; +PyAPI_DATA(PyTypeObject) PyDictIterValue_Type; +PyAPI_DATA(PyTypeObject) PyDictIterItem_Type; +PyAPI_DATA(PyTypeObject) PyDictKeys_Type; +PyAPI_DATA(PyTypeObject) PyDictItems_Type; +PyAPI_DATA(PyTypeObject) PyDictValues_Type; #define PyDict_Check(op) \ PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_DICT_SUBCLASS) #define PyDict_CheckExact(op) (Py_TYPE(op) == &PyDict_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) +/* This excludes Values, since they are not sets. */ +# 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); Index: Objects/dictobject.c =================================================================== --- Objects/dictobject.c (revision 60465) +++ Objects/dictobject.c (working copy) @@ -2041,6 +2041,18 @@ PyDoc_STRVAR(iteritems__doc__, "D.iteritems() -> an iterator over the (key, value) items of D"); +/* Forward */ +static PyObject *dictkeys_new(PyObject *); +static PyObject *dictitems_new(PyObject *); +static PyObject *dictvalues_new(PyObject *); + +PyDoc_STRVAR(viewkeys__doc__, + "D.keys() -> a set-like object providing a view on D's keys"); +PyDoc_STRVAR(viewitems__doc__, + "D.items() -> a set-like object providing a view on D's items"); +PyDoc_STRVAR(viewvalues__doc__, + "D.values() -> an object providing a view on D's values"); + static PyMethodDef mapp_methods[] = { {"__contains__",(PyCFunction)dict_contains, METH_O | METH_COEXIST, contains__doc__}, @@ -2062,6 +2074,12 @@ items__doc__}, {"values", (PyCFunction)dict_values, METH_NOARGS, values__doc__}, + {"viewkeys", (PyCFunction)dictkeys_new, METH_NOARGS, + viewkeys__doc__}, + {"viewitems", (PyCFunction)dictitems_new, METH_NOARGS, + viewitems__doc__}, + {"viewvalues", (PyCFunction)dictvalues_new, METH_NOARGS, + viewvalues__doc__}, {"update", (PyCFunction)dict_update, METH_VARARGS | METH_KEYWORDS, update__doc__}, {"fromkeys", (PyCFunction)dict_fromkeys, METH_VARARGS | METH_CLASS, @@ -2537,3 +2555,465 @@ dictiter_methods, /* tp_methods */ 0, }; + + +/***********************************************/ +/* View objects for keys(), items(), values(). */ +/***********************************************/ + +/* The instance lay-out is the same for all three; but the type differs. */ + +typedef struct { + PyObject_HEAD + PyDictObject *dv_dict; +} dictviewobject; + + +static void +dictview_dealloc(dictviewobject *dv) +{ + Py_XDECREF(dv->dv_dict); + PyObject_Del(dv); +} + +static Py_ssize_t +dictview_len(dictviewobject *dv) +{ + Py_ssize_t len = 0; + if (dv->dv_dict != NULL) + len = dv->dv_dict->ma_used; + return len; +} + +static PyObject * +dictview_new(PyObject *dict, PyTypeObject *type) +{ + dictviewobject *dv; + if (dict == NULL) { + PyErr_BadInternalCall(); + return NULL; + } + if (!PyDict_Check(dict)) { + /* XXX Get rid of this restriction later */ + PyErr_Format(PyExc_TypeError, + "%s() requires a dict argument, not '%s'", + type->tp_name, dict->ob_type->tp_name); + return NULL; + } + dv = PyObject_New(dictviewobject, type); + if (dv == NULL) + return NULL; + Py_INCREF(dict); + dv->dv_dict = (PyDictObject *)dict; + return (PyObject *)dv; +} + +/* TODO(guido): The views objects are not complete: + + * support more set operations + * support arbitrary mappings? + - either these should be static or exported in dictobject.h + - if public then they should probably be in builtins +*/ + +/* Return 1 if self is a subset of other, iterating over self; + 0 if not; -1 if an error occurred. */ +static int +all_contained_in(PyObject *self, PyObject *other) +{ + PyObject *iter = PyObject_GetIter(self); + int ok = 1; + + if (iter == NULL) + return -1; + for (;;) { + PyObject *next = PyIter_Next(iter); + if (next == NULL) { + if (PyErr_Occurred()) + ok = -1; + break; + } + ok = PySequence_Contains(other, next); + Py_DECREF(next); + if (ok <= 0) + break; + } + Py_DECREF(iter); + return ok; +} + +static PyObject * +dictview_richcompare(PyObject *self, PyObject *other, int op) +{ + Py_ssize_t len_self, len_other; + int ok; + PyObject *result; + + assert(self != NULL); + assert(PyDictViewSet_Check(self)); + assert(other != NULL); + + if (!PyAnySet_Check(other) && !PyDictViewSet_Check(other)) { + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; + } + + len_self = PyObject_Size(self); + if (len_self < 0) + return NULL; + len_other = PyObject_Size(other); + if (len_other < 0) + return NULL; + + ok = 0; + switch(op) { + + case Py_NE: + case Py_EQ: + if (len_self == len_other) + ok = all_contained_in(self, other); + if (op == Py_NE && ok >= 0) + ok = !ok; + break; + + case Py_LT: + if (len_self < len_other) + ok = all_contained_in(self, other); + break; + + case Py_LE: + if (len_self <= len_other) + ok = all_contained_in(self, other); + break; + + case Py_GT: + if (len_self > len_other) + ok = all_contained_in(other, self); + break; + + case Py_GE: + if (len_self >= len_other) + ok = all_contained_in(other, self); + break; + + } + if (ok < 0) + return NULL; + result = ok ? Py_True : Py_False; + Py_INCREF(result); + return result; +} + +/*** dict_keys ***/ + +static PyObject * +dictkeys_iter(dictviewobject *dv) +{ + if (dv->dv_dict == NULL) { + Py_RETURN_NONE; + } + return dictiter_new(dv->dv_dict, &PyDictIterKey_Type); +} + +static int +dictkeys_contains(dictviewobject *dv, PyObject *obj) +{ + if (dv->dv_dict == NULL) + return 0; + return PyDict_Contains((PyObject *)dv->dv_dict, obj); +} + +static PySequenceMethods dictkeys_as_sequence = { + (lenfunc)dictview_len, /* sq_length */ + 0, /* sq_concat */ + 0, /* sq_repeat */ + 0, /* sq_item */ + 0, /* sq_slice */ + 0, /* sq_ass_item */ + 0, /* sq_ass_slice */ + (objobjproc)dictkeys_contains, /* sq_contains */ +}; + +static PyObject* +dictviews_sub(PyObject* self, PyObject *other) +{ + PyObject *result = PySet_New(self); + PyObject *tmp; + if (result == NULL) + return NULL; + + tmp = PyObject_CallMethod(result, "difference_update", "O", other); + if (tmp == NULL) { + Py_DECREF(result); + return NULL; + } + + Py_DECREF(tmp); + return result; +} + +static PyObject* +dictviews_and(PyObject* self, PyObject *other) +{ + PyObject *result = PySet_New(self); + PyObject *tmp; + if (result == NULL) + return NULL; + + tmp = PyObject_CallMethod(result, "intersection_update", "O", other); + if (tmp == NULL) { + Py_DECREF(result); + return NULL; + } + + Py_DECREF(tmp); + return result; +} + +static PyObject* +dictviews_or(PyObject* self, PyObject *other) +{ + PyObject *result = PySet_New(self); + PyObject *tmp; + if (result == NULL) + return NULL; + + tmp = PyObject_CallMethod(result, "update", "O", other); + if (tmp == NULL) { + Py_DECREF(result); + return NULL; + } + + Py_DECREF(tmp); + return result; +} + +static PyObject* +dictviews_xor(PyObject* self, PyObject *other) +{ + PyObject *result = PySet_New(self); + PyObject *tmp; + if (result == NULL) + return NULL; + + tmp = PyObject_CallMethod(result, "symmetric_difference_update", "O", + other); + if (tmp == NULL) { + Py_DECREF(result); + return NULL; + } + + Py_DECREF(tmp); + return result; +} + +static PyNumberMethods dictviews_as_number = { + 0, /*nb_add*/ + (binaryfunc)dictviews_sub, /*nb_subtract*/ + 0, /*nb_multiply*/ + 0, /*nb_remainder*/ + 0, /*nb_divmod*/ + 0, /*nb_power*/ + 0, /*nb_negative*/ + 0, /*nb_positive*/ + 0, /*nb_absolute*/ + 0, /*nb_bool*/ + 0, /*nb_invert*/ + 0, /*nb_lshift*/ + 0, /*nb_rshift*/ + (binaryfunc)dictviews_and, /*nb_and*/ + (binaryfunc)dictviews_xor, /*nb_xor*/ + (binaryfunc)dictviews_or, /*nb_or*/ +}; + +static PyMethodDef dictkeys_methods[] = { + {NULL, NULL} /* sentinel */ +}; + +PyTypeObject PyDictKeys_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + "dict_keys", /* tp_name */ + sizeof(dictviewobject), /* tp_basicsize */ + 0, /* tp_itemsize */ + /* methods */ + (destructor)dictview_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + &dictviews_as_number, /* tp_as_number */ + &dictkeys_as_sequence, /* 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, /* tp_flags */ + 0, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + dictview_richcompare, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + (getiterfunc)dictkeys_iter, /* tp_iter */ + 0, /* tp_iternext */ + dictkeys_methods, /* tp_methods */ + 0, +}; + +static PyObject * +dictkeys_new(PyObject *dict) +{ + return dictview_new(dict, &PyDictKeys_Type); +} + +/*** dict_items ***/ + +static PyObject * +dictitems_iter(dictviewobject *dv) +{ + if (dv->dv_dict == NULL) { + Py_RETURN_NONE; + } + return dictiter_new(dv->dv_dict, &PyDictIterItem_Type); +} + +static int +dictitems_contains(dictviewobject *dv, PyObject *obj) +{ + PyObject *key, *value, *found; + if (dv->dv_dict == NULL) + return 0; + if (!PyTuple_Check(obj) || PyTuple_GET_SIZE(obj) != 2) + return 0; + key = PyTuple_GET_ITEM(obj, 0); + value = PyTuple_GET_ITEM(obj, 1); + found = PyDict_GetItem((PyObject *)dv->dv_dict, key); + if (found == NULL) { + if (PyErr_Occurred()) + return -1; + return 0; + } + return PyObject_RichCompareBool(value, found, Py_EQ); +} + +static PySequenceMethods dictitems_as_sequence = { + (lenfunc)dictview_len, /* sq_length */ + 0, /* sq_concat */ + 0, /* sq_repeat */ + 0, /* sq_item */ + 0, /* sq_slice */ + 0, /* sq_ass_item */ + 0, /* sq_ass_slice */ + (objobjproc)dictitems_contains, /* sq_contains */ +}; + +static PyMethodDef dictitems_methods[] = { + {NULL, NULL} /* sentinel */ +}; + +PyTypeObject PyDictItems_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + "dict_items", /* tp_name */ + sizeof(dictviewobject), /* tp_basicsize */ + 0, /* tp_itemsize */ + /* methods */ + (destructor)dictview_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + &dictviews_as_number, /* tp_as_number */ + &dictitems_as_sequence, /* 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, /* tp_flags */ + 0, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + dictview_richcompare, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + (getiterfunc)dictitems_iter, /* tp_iter */ + 0, /* tp_iternext */ + dictitems_methods, /* tp_methods */ + 0, +}; + +static PyObject * +dictitems_new(PyObject *dict) +{ + return dictview_new(dict, &PyDictItems_Type); +} + +/*** dict_values ***/ + +static PyObject * +dictvalues_iter(dictviewobject *dv) +{ + if (dv->dv_dict == NULL) { + Py_RETURN_NONE; + } + return dictiter_new(dv->dv_dict, &PyDictIterValue_Type); +} + +static PySequenceMethods dictvalues_as_sequence = { + (lenfunc)dictview_len, /* sq_length */ + 0, /* sq_concat */ + 0, /* sq_repeat */ + 0, /* sq_item */ + 0, /* sq_slice */ + 0, /* sq_ass_item */ + 0, /* sq_ass_slice */ + (objobjproc)0, /* sq_contains */ +}; + +static PyMethodDef dictvalues_methods[] = { + {NULL, NULL} /* sentinel */ +}; + +PyTypeObject PyDictValues_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + "dict_values", /* tp_name */ + sizeof(dictviewobject), /* tp_basicsize */ + 0, /* tp_itemsize */ + /* methods */ + (destructor)dictview_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + &dictvalues_as_sequence, /* 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, /* tp_flags */ + 0, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + (getiterfunc)dictvalues_iter, /* tp_iter */ + 0, /* tp_iternext */ + dictvalues_methods, /* tp_methods */ + 0, +}; + +static PyObject * +dictvalues_new(PyObject *dict) +{ + return dictview_new(dict, &PyDictValues_Type); +} Index: Lib/test/test_dictviews.py =================================================================== --- Lib/test/test_dictviews.py (revision 0) +++ Lib/test/test_dictviews.py (revision 0) @@ -0,0 +1,76 @@ +import unittest +from test import test_support + +class DictSetTest(unittest.TestCase): + + def test_constructors_not_callable(self): + kt = type({}.viewkeys()) + self.assertRaises(TypeError, kt, {}) + self.assertRaises(TypeError, kt) + it = type({}.viewitems()) + self.assertRaises(TypeError, it, {}) + self.assertRaises(TypeError, it) + vt = type({}.viewvalues()) + self.assertRaises(TypeError, vt, {}) + self.assertRaises(TypeError, vt) + + def test_dict_keys(self): + d = {1: 10, "a": "ABC"} + keys = d.viewkeys() + self.assertEqual(len(keys), 2) + self.assertEqual(set(keys), set([1, "a"])) + self.assertEqual(keys, set([1, "a"])) + self.assertNotEqual(keys, set([1, "a", "b"])) + self.assertNotEqual(keys, set([1, "b"])) + self.assertNotEqual(keys, set([1])) + self.assertNotEqual(keys, 42) + self.assert_(1 in keys) + self.assert_("a" in keys) + self.assert_(10 not in keys) + self.assert_("Z" not in keys) + self.assertEqual(d.viewkeys(), d.viewkeys()) + e = {1: 11, "a": "def"} + self.assertEqual(d.viewkeys(), e.viewkeys()) + del e["a"] + self.assertNotEqual(d.viewkeys(), e.viewkeys()) + + def test_dict_items(self): + d = {1: 10, "a": "ABC"} + items = d.viewitems() + self.assertEqual(len(items), 2) + self.assertEqual(set(items), set([(1, 10), ("a", "ABC")])) + self.assertEqual(items, set([(1, 10), ("a", "ABC")])) + self.assertNotEqual(items, set([(1, 10), ("a", "ABC"), "junk"])) + self.assertNotEqual(items, set([(1, 10), ("a", "def")])) + self.assertNotEqual(items, set([(1, 10)])) + self.assertNotEqual(items, 42) + self.assert_((1, 10) in items) + self.assert_(("a", "ABC") in items) + self.assert_((1, 11) not in items) + self.assert_(1 not in items) + self.assert_(() not in items) + self.assert_((1,) not in items) + self.assert_((1, 2, 3) not in items) + self.assertEqual(d.viewitems(), d.viewitems()) + e = d.copy() + self.assertEqual(d.viewitems(), e.viewitems()) + e["a"] = "def" + self.assertNotEqual(d.viewitems(), e.viewitems()) + + def test_dict_mixed_keys_items(self): + d = {(1, 1): 11, (2, 2): 22} + e = {1: 1, 2: 2} + self.assertEqual(d.viewkeys(), e.viewitems()) + self.assertNotEqual(d.viewitems(), e.viewkeys()) + + def test_dict_values(self): + d = {1: 10, "a": "ABC"} + values = d.viewvalues() + self.assertEqual(set(values), set([10, "ABC"])) + self.assertEqual(len(values), 2) + +def test_main(): + test_support.run_unittest(DictSetTest) + +if __name__ == "__main__": + test_main()