Issue #1967: Backport dictionary views.

This commit is contained in:
Alexandre Vassalotti 2010-01-11 23:17:10 +00:00
parent 7a8df80285
commit 69eb51697c
6 changed files with 725 additions and 0 deletions

View File

@ -540,6 +540,13 @@ Glossary
object has a type. An object's type is accessible as its
:attr:`__class__` attribute or can be retrieved with ``type(obj)``.
view
The objects returned from :meth:`dict.viewkeys`, :meth:`dict.viewvalues`,
and :meth:`dict.viewitems` are called dictionary views. They are lazy
sequences that will see changes in the underlying dictionary. To force
the dictionary view to become a full list use ``list(dictview)``. See
:ref:`dict-views`.
virtual machine
A computer defined entirely in software. Python's virtual machine
executes the :term:`bytecode` emitted by the bytecode compiler.

View File

@ -2106,6 +2106,121 @@ pairs within braces, for example: ``{'jack': 4098, 'sjoerd': 4127}`` or ``{4098:
Return a copy of the dictionary's list of values. See the note for
:meth:`dict.items`.
.. method:: viewitems()
Return a new view of the dictionary's items (``(key, value)`` pairs). See
below for documentation of view objects.
.. versionadded:: 2.7
.. method:: viewkeys()
Return a new view of the dictionary's keys. See below for documentation of
view objects.
.. versionadded:: 2.7
.. method:: viewvalues()
Return a new view of the dictionary's values. See below for documentation of
view objects.
.. versionadded:: 2.7
.. _dict-views:
Dictionary view objects
-----------------------
The objects returned by :meth:`dict.viewkeys`, :meth:`dict.viewvalues` and
:meth:`dict.viewitems` are *view objects*. They provide a dynamic view on the
dictionary's entries, which means that when the dictionary changes, the view
reflects these changes.
Dictionary views can be iterated over to yield their respective data, and
support membership tests:
.. describe:: len(dictview)
Return the number of entries in the dictionary.
.. describe:: iter(dictview)
Return an iterator over the keys, values or items (represented as tuples of
``(key, value)``) in the dictionary.
Keys and values are iterated over in an arbitrary order which is non-random,
varies across Python implementations, and depends on the dictionary's history
of insertions and deletions. If keys, values and items views are iterated
over with no intervening modifications to the dictionary, the order of items
will directly correspond. This allows the creation of ``(value, key)`` pairs
using :func:`zip`: ``pairs = zip(d.values(), d.keys())``. Another way to
create the same list is ``pairs = [(v, k) for (k, v) in d.items()]``.
Iterating views while adding or deleting entries in the dictionary may raise
a :exc:`RuntimeError` or fail to iterate over all entries.
.. describe:: x in dictview
Return ``True`` if *x* is in the underlying dictionary's keys, values or
items (in the latter case, *x* should be a ``(key, value)`` tuple).
Keys views are set-like since their entries are unique and hashable. If all
values are hashable, so that (key, value) pairs are unique and hashable, then
the items view is also set-like. (Values views are not treated as set-like
since the entries are generally not unique.) Then these set operations are
available ("other" refers either to another view or a set):
.. describe:: dictview & other
Return the intersection of the dictview and the other object as a new set.
.. describe:: dictview | other
Return the union of the dictview and the other object as a new set.
.. describe:: dictview - other
Return the difference between the dictview and the other object (all elements
in *dictview* that aren't in *other*) as a new set.
.. describe:: dictview ^ other
Return the symmetric difference (all elements either in *dictview* or
*other*, but not in both) of the dictview and the other object as a new set.
An example of dictionary view usage::
>>> dishes = {'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500}
>>> keys = dishes.viewkeys()
>>> values = dishes.viewvalues()
>>> # iteration
>>> n = 0
>>> for val in values:
... n += val
>>> print(n)
504
>>> # keys and values are iterated over in the same order
>>> list(keys)
['eggs', 'bacon', 'sausage', 'spam']
>>> list(values)
[2, 1, 1, 500]
>>> # view objects are dynamic and reflect dict changes
>>> del dishes['eggs']
>>> del dishes['sausage']
>>> list(keys)
['spam', 'bacon']
>>> # set operations
>>> keys & {'eggs', 'bacon', 'salad'}
{'bacon'}
.. _bltin-file-objects:

View File

@ -89,10 +89,22 @@ struct _dictobject {
};
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);

View File

@ -0,0 +1,84 @@
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_dict_repr(self):
d = {1: 10, "a": "ABC"}
self.assertTrue(isinstance(repr(d), str))
self.assertTrue(isinstance(repr(d.viewitems()), str))
self.assertTrue(isinstance(repr(d.viewkeys()), str))
self.assertTrue(isinstance(repr(d.viewvalues()), str))
def test_main():
test_support.run_unittest(DictSetTest)
if __name__ == "__main__":
test_main()

View File

@ -18,6 +18,8 @@ Core and Builtins
- Issue #2333: Backport set and dict comprehensions syntax from Python 3.x.
- Issue #1967: Backport dictionary views from Python 3.x.
Library
-------

View File

@ -2184,6 +2184,18 @@ PyDoc_STRVAR(itervalues__doc__,
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.viewkeys() -> a set-like object providing a view on D's keys");
PyDoc_STRVAR(viewitems__doc__,
"D.viewitems() -> a set-like object providing a view on D's items");
PyDoc_STRVAR(viewvalues__doc__,
"D.viewvalues() -> an object providing a view on D's values");
static PyMethodDef mapp_methods[] = {
{"__contains__",(PyCFunction)dict_contains, METH_O | METH_COEXIST,
contains__doc__},
@ -2207,6 +2219,12 @@ static PyMethodDef mapp_methods[] = {
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,
@ -2700,3 +2718,490 @@ PyTypeObject PyDictIterItem_Type = {
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_GC_Del(dv);
}
static int
dictview_traverse(dictviewobject *dv, visitproc visit, void *arg)
{
Py_VISIT(dv->dv_dict);
return 0;
}
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_GC_New(dictviewobject, type);
if (dv == NULL)
return NULL;
Py_INCREF(dict);
dv->dv_dict = (PyDictObject *)dict;
_PyObject_GC_TRACK(dv);
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;
}
static PyObject *
dictview_repr(dictviewobject *dv)
{
PyObject *seq;
PyObject *seq_str;
PyObject *result;
seq = PySequence_List((PyObject *)dv);
if (seq == NULL)
return NULL;
seq_str = PyObject_Repr(seq);
result = PyString_FromFormat("%s(%s)", Py_TYPE(dv)->tp_name, seq_str);
Py_DECREF(seq_str);
Py_DECREF(seq);
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_reserved */
(reprfunc)dictview_repr, /* 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 | Py_TPFLAGS_HAVE_GC,/* tp_flags */
0, /* tp_doc */
(traverseproc)dictview_traverse, /* 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_reserved */
(reprfunc)dictview_repr, /* 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 | Py_TPFLAGS_HAVE_GC,/* tp_flags */
0, /* tp_doc */
(traverseproc)dictview_traverse, /* 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_reserved */
(reprfunc)dictview_repr, /* 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 | Py_TPFLAGS_HAVE_GC,/* tp_flags */
0, /* tp_doc */
(traverseproc)dictview_traverse, /* 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);
}