Index: Python/ceval.c =================================================================== --- Python/ceval.c (revision 60424) +++ Python/ceval.c (working copy) @@ -1806,6 +1806,17 @@ if (err == 0) continue; break; + case STORE_VIEWATTR: + w = GETITEM(names, oparg); + v = TOP(); + u = SECOND(); + STACKADJ(-2); + err = _PyObject_SetViewAttr(v, w, u); /* v.w = u */ + Py_DECREF(v); + Py_DECREF(u); + if (err == 0) continue; + break; + case DELETE_ATTR: w = GETITEM(names, oparg); v = POP(); @@ -1814,6 +1825,13 @@ Py_DECREF(v); break; + case DELETE_VIEWATTR: + w = GETITEM(names, oparg); + v = POP(); + err = _PyObject_SetViewAttr(v, w, (PyObject *)NULL); + Py_DECREF(v); + break; + case STORE_GLOBAL: w = GETITEM(names, oparg); v = POP(); @@ -2023,6 +2041,16 @@ if (x != NULL) continue; break; + case LOAD_VIEWATTR: + w = GETITEM(names, oparg); + v = TOP(); + x = _PyObject_GetViewAttr(v, w); + Py_DECREF(v); + SET_TOP(x); + if (x != NULL) + continue; + break; + case COMPARE_OP: w = POP(); v = TOP(); Index: Python/compile.c =================================================================== --- Python/compile.c (revision 60424) +++ Python/compile.c (working copy) @@ -155,6 +155,7 @@ static basicblock *compiler_use_new_block(struct compiler *); static int compiler_error(struct compiler *, const char *); static int compiler_nameop(struct compiler *, identifier, expr_context_ty); +static int compiler_attrop(struct compiler *, expr_ty); static PyCodeObject *compiler_mod(struct compiler *, mod_ty); static int compiler_visit_stmt(struct compiler *, stmt_ty); @@ -793,8 +794,10 @@ return 1; case STORE_ATTR: + case STORE_VIEWATTR: return -2; case DELETE_ATTR: + case DELETE_VIEWATTR: return -1; case STORE_GLOBAL: return -1; @@ -812,6 +815,7 @@ case BUILD_MAP: return 1; case LOAD_ATTR: + case LOAD_VIEWATTR: return 0; case COMPARE_OP: return -1; @@ -2966,31 +2970,7 @@ break; /* The following exprs can be assignment targets. */ case Attribute_kind: - if (e->v.Attribute.ctx != AugStore) - VISIT(c, expr, e->v.Attribute.value); - switch (e->v.Attribute.ctx) { - case AugLoad: - ADDOP(c, DUP_TOP); - /* Fall through to load */ - case Load: - ADDOP_NAME(c, LOAD_ATTR, e->v.Attribute.attr, names); - break; - case AugStore: - ADDOP(c, ROT_TWO); - /* Fall through to save */ - case Store: - ADDOP_NAME(c, STORE_ATTR, e->v.Attribute.attr, names); - break; - case Del: - ADDOP_NAME(c, DELETE_ATTR, e->v.Attribute.attr, names); - break; - case Param: - default: - PyErr_SetString(PyExc_SystemError, - "param invalid in attribute expression"); - return 0; - } - break; + return compiler_attrop(c, e); case Subscript_kind: switch (e->v.Subscript.ctx) { case AugLoad: @@ -3076,7 +3056,51 @@ return 1; } + /* Helper to handle dict views future-import magic */ static int +compiler_attrop(struct compiler *c, expr_ty e) +{ + int needviews = 0; + + if (c->c_flags && (c->c_flags->cf_flags & CO_FUTURE_DICT_VIEWS)) { + const char *attrstr = PyString_AS_STRING(e->v.Attribute.attr); + if (strcmp(attrstr, "keys") == 0 || + strcmp(attrstr, "items") == 0 || + strcmp(attrstr, "values") == 0) + needviews = 1; + } + + if (e->v.Attribute.ctx != AugStore) + VISIT(c, expr, e->v.Attribute.value); + switch (e->v.Attribute.ctx) { + case AugLoad: + ADDOP(c, DUP_TOP); + /* Fall through to load */ + case Load: + ADDOP_NAME(c, needviews ? LOAD_VIEWATTR : LOAD_ATTR, + e->v.Attribute.attr, names); + break; + case AugStore: + ADDOP(c, ROT_TWO); + /* Fall through to save */ + case Store: + ADDOP_NAME(c, needviews ? STORE_VIEWATTR : STORE_ATTR, + e->v.Attribute.attr, names); + break; + case Del: + ADDOP_NAME(c, needviews ? DELETE_VIEWATTR : DELETE_ATTR, + e->v.Attribute.attr, names); + break; + case Param: + default: + PyErr_SetString(PyExc_SystemError, + "param invalid in attribute expression"); + return 0; + } + return 1; +} + +static int compiler_push_fblock(struct compiler *c, enum fblocktype t, basicblock *b) { struct fblockinfo *f; Index: Python/future.c =================================================================== --- Python/future.c (revision 60424) +++ Python/future.c (working copy) @@ -33,6 +33,8 @@ ff->ff_features |= CO_FUTURE_ABSOLUTE_IMPORT; } else if (strcmp(feature, FUTURE_WITH_STATEMENT) == 0) { ff->ff_features |= CO_FUTURE_WITH_STATEMENT; + } else if (strcmp(feature, FUTURE_DICT_VIEWS) == 0) { + ff->ff_features |= CO_FUTURE_DICT_VIEWS; } else if (strcmp(feature, "braces") == 0) { PyErr_SetString(PyExc_SyntaxError, "not a chance"); Index: Python/bltinmodule.c =================================================================== --- Python/bltinmodule.c (revision 60424) +++ Python/bltinmodule.c (working copy) @@ -5,6 +5,7 @@ #include "node.h" #include "code.h" #include "eval.h" +#include "frameobject.h" #include @@ -753,6 +754,7 @@ { PyObject *v, *result, *dflt = NULL; PyObject *name; + const char *sname; if (!PyArg_UnpackTuple(args, "getattr", 2, 3, &v, &name, &dflt)) return NULL; @@ -769,7 +771,16 @@ "getattr(): attribute name must be string"); return NULL; } - result = PyObject_GetAttr(v, name); + sname = PyString_AS_STRING(name); + /* Do some magic to see if the caller wanted dictviews or not */ + if ((strcmp(sname, "keys") == 0 || + strcmp(sname, "items") == 0 || + strcmp(sname, "values") == 0) && + (PyThreadState_GET()->frame->f_code->co_flags & + CO_FUTURE_DICT_VIEWS)) { + result = _PyObject_GetViewAttr(v, name); + } else + result = PyObject_GetAttr(v, name); if (result == NULL && dflt != NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) { @@ -1021,11 +1032,36 @@ PyObject *v; PyObject *name; PyObject *value; + const char *sname; + int result; if (!PyArg_UnpackTuple(args, "setattr", 3, 3, &v, &name, &value)) return NULL; - if (PyObject_SetAttr(v, name, value) != 0) +#ifdef Py_USING_UNICODE + if (PyUnicode_Check(name)) { + name = _PyUnicode_AsDefaultEncodedString(name, NULL); + if (name == NULL) + return NULL; + } +#endif + + if (!PyString_Check(name)) { + PyErr_SetString(PyExc_TypeError, + "setattr(): attribute name must be string"); return NULL; + } + sname = PyString_AS_STRING(name); + /* Do some magic to see if the caller wanted dictviews or not */ + if ((strcmp(sname, "keys") == 0 || + strcmp(sname, "items") == 0 || + strcmp(sname, "values") == 0) && + (PyThreadState_GET()->frame->f_code->co_flags & + CO_FUTURE_DICT_VIEWS)) { + result = _PyObject_SetViewAttr(v, name, value); + } else + result = PyObject_SetAttr(v, name, value); + if (result != 0) + return NULL; Py_INCREF(Py_None); return Py_None; } @@ -1042,11 +1078,36 @@ { PyObject *v; PyObject *name; + const char *sname; + int result; if (!PyArg_UnpackTuple(args, "delattr", 2, 2, &v, &name)) return NULL; - if (PyObject_SetAttr(v, name, (PyObject *)NULL) != 0) +#ifdef Py_USING_UNICODE + if (PyUnicode_Check(name)) { + name = _PyUnicode_AsDefaultEncodedString(name, NULL); + if (name == NULL) + return NULL; + } +#endif + + if (!PyString_Check(name)) { + PyErr_SetString(PyExc_TypeError, + "delattr(): attribute name must be string"); return NULL; + } + sname = PyString_AS_STRING(name); + /* Do some magic to see if the caller wanted dictviews or not */ + if ((strcmp(sname, "keys") == 0 || + strcmp(sname, "items") == 0 || + strcmp(sname, "values") == 0) && + (PyThreadState_GET()->frame->f_code->co_flags & + CO_FUTURE_DICT_VIEWS)) { + result = _PyObject_SetViewAttr(v, name, (PyObject *)NULL); + } else + result = PyObject_SetAttr(v, name, (PyObject *)NULL); + if (result != 0) + return NULL; Py_INCREF(Py_None); return Py_None; } Index: Include/opcode.h =================================================================== --- Include/opcode.h (revision 60424) +++ Include/opcode.h (working copy) @@ -104,12 +104,13 @@ #define COMPARE_OP 106 /* Comparison operator */ #define IMPORT_NAME 107 /* Index in name list */ #define IMPORT_FROM 108 /* Index in name list */ - +#define LOAD_VIEWATTR 109 /* Index in name list */ #define JUMP_FORWARD 110 /* Number of bytes to skip */ #define JUMP_IF_FALSE 111 /* "" */ #define JUMP_IF_TRUE 112 /* "" */ #define JUMP_ABSOLUTE 113 /* Target byte offset from beginning of code */ - +#define STORE_VIEWATTR 114 /* Index in name list */ +#define DELETE_VIEWATTR 115 /* Index in name list */ #define LOAD_GLOBAL 116 /* Index in name list */ #define CONTINUE_LOOP 119 /* Start of loop (absolute) */ Index: Include/dictobject.h =================================================================== --- Include/dictobject.h (revision 60424) +++ 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: Include/code.h =================================================================== --- Include/code.h (revision 60424) +++ Include/code.h (working copy) @@ -48,6 +48,7 @@ #define CO_FUTURE_DIVISION 0x2000 #define CO_FUTURE_ABSOLUTE_IMPORT 0x4000 /* do absolute imports by default */ #define CO_FUTURE_WITH_STATEMENT 0x8000 +#define CO_FUTURE_DICT_VIEWS 0x10000 /* This should be defined if a future statement modifies the syntax. For example, when a keyword is added. Index: Include/object.h =================================================================== --- Include/object.h (revision 60424) +++ Include/object.h (working copy) @@ -417,7 +417,9 @@ PyAPI_FUNC(int) PyObject_SetAttrString(PyObject *, const char *, PyObject *); PyAPI_FUNC(int) PyObject_HasAttrString(PyObject *, const char *); PyAPI_FUNC(PyObject *) PyObject_GetAttr(PyObject *, PyObject *); +PyAPI_FUNC(PyObject *) _PyObject_GetViewAttr(PyObject *, PyObject *); PyAPI_FUNC(int) PyObject_SetAttr(PyObject *, PyObject *, PyObject *); +PyAPI_FUNC(int) _PyObject_SetViewAttr(PyObject *, PyObject *, PyObject *); PyAPI_FUNC(int) PyObject_HasAttr(PyObject *, PyObject *); PyAPI_FUNC(PyObject **) _PyObject_GetDictPtr(PyObject *); PyAPI_FUNC(PyObject *) PyObject_SelfIter(PyObject *); Index: Include/pythonrun.h =================================================================== --- Include/pythonrun.h (revision 60424) +++ Include/pythonrun.h (working copy) @@ -8,7 +8,7 @@ #endif #define PyCF_MASK (CO_FUTURE_DIVISION | CO_FUTURE_ABSOLUTE_IMPORT | \ - CO_FUTURE_WITH_STATEMENT) + CO_FUTURE_WITH_STATEMENT | CO_FUTURE_DICT_VIEWS) #define PyCF_MASK_OBSOLETE (CO_NESTED) #define PyCF_SOURCE_IS_UTF8 0x0100 #define PyCF_DONT_IMPLY_DEDENT 0x0200 Index: Include/compile.h =================================================================== --- Include/compile.h (revision 60424) +++ Include/compile.h (working copy) @@ -24,6 +24,7 @@ #define FUTURE_DIVISION "division" #define FUTURE_ABSOLUTE_IMPORT "absolute_import" #define FUTURE_WITH_STATEMENT "with_statement" +#define FUTURE_DICT_VIEWS "dict_views" struct _mod; /* Declare the existence of this type */ PyAPI_FUNC(PyCodeObject *) PyAST_Compile(struct _mod *, const char *, Index: Objects/object.c =================================================================== --- Objects/object.c (revision 60424) +++ Objects/object.c (working copy) @@ -1110,6 +1110,33 @@ } PyObject * +_PyObject_GetViewAttr(PyObject *v, PyObject *name) +{ + /* Wrapper around PyObject_GetAttr that translates + dict.keys/items/values into dict.viewkeys/viewitems/viewvalues + for all subclasses of dict. It doesn't care about dict-like + objects even if they have view* attrs, and doesn't check + whether the attribute is actually one of keys/items/values */ + if (PyDict_Check(v)) { + static PyObject *viewstr; + PyObject *result; + + if (viewstr == NULL) { + viewstr = PyString_InternFromString("view"); + if (viewstr == NULL) + return NULL; + } + name = PyNumber_Add(viewstr, name); + if (name == NULL) + return NULL; + result = PyObject_GetAttr(v, name); + Py_DECREF(name); + return result; + } + return PyObject_GetAttr(v, name); +} + +PyObject * PyObject_GetAttr(PyObject *v, PyObject *name) { PyTypeObject *tp = Py_TYPE(v); @@ -1156,6 +1183,29 @@ } int +_PyObject_SetViewAttr(PyObject *v, PyObject *name, PyObject *value) +{ + /* Like _PyObject_GetViewAttr(), but for setting/deleting */ + if (PyDict_Check(v)) { + static PyObject *viewstr; + int result; + + if (viewstr == NULL) { + viewstr = PyString_InternFromString("view"); + if (viewstr == NULL) + return -1; + } + name = PyNumber_Add(viewstr, name); + if (name == NULL) + return -1; + result = PyObject_SetAttr(v, name, value); + Py_DECREF(name); + return result; + } + return PyObject_SetAttr(v, name, value); +} + +int PyObject_SetAttr(PyObject *v, PyObject *name, PyObject *value) { PyTypeObject *tp = Py_TYPE(v); Index: Objects/dictobject.c =================================================================== --- Objects/dictobject.c (revision 60424) +++ 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/__future__.py =================================================================== --- Lib/__future__.py (revision 60424) +++ Lib/__future__.py (working copy) @@ -53,6 +53,7 @@ "division", "absolute_import", "with_statement", + "dict_views", ] __all__ = ["all_feature_names"] + all_feature_names @@ -66,6 +67,7 @@ CO_FUTURE_DIVISION = 0x2000 # division CO_FUTURE_ABSOLUTE_IMPORT = 0x4000 # perform absolute imports by default CO_FUTURE_WITH_STATEMENT = 0x8000 # with statement +CO_FUTURE_DICT_VIEWS = 0x10000 class _Feature: def __init__(self, optionalRelease, mandatoryRelease, compiler_flag): @@ -114,3 +116,7 @@ with_statement = _Feature((2, 5, 0, "alpha", 1), (2, 6, 0, "alpha", 0), CO_FUTURE_WITH_STATEMENT) + +dict_views = _Feature((2, 6, 0, "alpha", 1), + (3, 0, 0, "alpha", 0), + CO_FUTURE_DICT_VIEWS) Index: Lib/compiler/consts.py =================================================================== --- Lib/compiler/consts.py (revision 60424) +++ Lib/compiler/consts.py (working copy) @@ -19,3 +19,4 @@ CO_FUTURE_DIVISION = 0x2000 CO_FUTURE_ABSIMPORT = 0x4000 CO_FUTURE_WITH_STATEMENT = 0x8000 +CO_FUTURE_DICT_VIEWS = 0x10000 Index: Lib/compiler/future.py =================================================================== --- Lib/compiler/future.py (revision 60424) +++ Lib/compiler/future.py (working copy) @@ -16,7 +16,7 @@ class FutureParser: features = ("nested_scopes", "generators", "division", - "absolute_import", "with_statement") + "absolute_import", "with_statement", "dict_views") def __init__(self): self.found = {} # set Index: Lib/compiler/pycodegen.py =================================================================== --- Lib/compiler/pycodegen.py (revision 60424) +++ Lib/compiler/pycodegen.py (working copy) @@ -10,7 +10,7 @@ from compiler.consts import SC_LOCAL, SC_GLOBAL, SC_FREE, SC_CELL from compiler.consts import (CO_VARARGS, CO_VARKEYWORDS, CO_NEWLOCALS, CO_NESTED, CO_GENERATOR, CO_FUTURE_DIVISION, - CO_FUTURE_ABSIMPORT, CO_FUTURE_WITH_STATEMENT) + CO_FUTURE_ABSIMPORT, CO_FUTURE_WITH_STATEMENT, CO_FUTURE_DICT_VIEWS) from compiler.pyassem import TupleArg # XXX The version-specific code can go, since this code only works with 2.x. @@ -218,6 +218,8 @@ self.graph.setFlag(CO_FUTURE_ABSIMPORT) elif feature == "with_statement": self.graph.setFlag(CO_FUTURE_WITH_STATEMENT) + elif feature == "dict_views": + self.graph.setFlag(CO_FUTURE_DICT_VIEWS) def initClass(self): """This method is called once for each class""" @@ -929,9 +931,18 @@ for elt in elts[1:]: self.emit('LOAD_ATTR', elt) + def _checkViewAttr(self, regop, viewop, attrname): + if (self.graph.checkFlag(CO_FUTURE_DICT_VIEWS) and + attrname in ('keys', 'items', 'values')): + return viewop + else: + return regop + def visitGetattr(self, node): self.visit(node.expr) - self.emit('LOAD_ATTR', self.mangle(node.attrname)) + load_op = self._checkViewAttr('LOAD_ATTR', 'LOAD_VIEWATTR', + node.attrname) + self.emit(load_op, self.mangle(node.attrname)) # next five implement assignments @@ -958,9 +969,13 @@ def visitAssAttr(self, node): self.visit(node.expr) if node.flags == 'OP_ASSIGN': - self.emit('STORE_ATTR', self.mangle(node.attrname)) + op = self._checkViewAttr('STORE_ATTR', 'STORE_VIEWATTR', + node.attrname) + self.emit(op, self.mangle(node.attrname)) elif node.flags == 'OP_DELETE': - self.emit('DELETE_ATTR', self.mangle(node.attrname)) + op = self._checkViewAttr('DELETE_ATTR', 'DELETE_VIEWATTR', + node.attrname) + self.emit(op, self.mangle(node.attrname)) else: print "warning: unexpected flags:", node.flags print node @@ -1016,10 +1031,14 @@ if mode == "load": self.visit(node.expr) self.emit('DUP_TOP') - self.emit('LOAD_ATTR', self.mangle(node.attrname)) + op = self._checkViewAttr('LOAD_ATTR', 'LOAD_VIEWATTR', + node.attrname) + self.emit(op, self.mangle(node.attrname)) elif mode == "store": self.emit('ROT_TWO') - self.emit('STORE_ATTR', self.mangle(node.attrname)) + op = self._checkViewAttr('STORE_ATTR', 'STORE_VIEWATTR', + node.attrname) + self.emit(op, self.mangle(node.attrname)) def visitAugSlice(self, node, mode): if mode == "load": Index: Lib/compiler/pyassem.py =================================================================== --- Lib/compiler/pyassem.py (revision 60424) +++ Lib/compiler/pyassem.py (working copy) @@ -533,6 +533,9 @@ _convert_STORE_ATTR = _convert_NAME _convert_LOAD_ATTR = _convert_NAME _convert_DELETE_ATTR = _convert_NAME + _convert_STORE_VIEWATTR = _convert_NAME + _convert_LOAD_VIEWATTR = _convert_NAME + _convert_DELETE_VIEWATTR = _convert_NAME _convert_LOAD_GLOBAL = _convert_NAME _convert_STORE_GLOBAL = _convert_NAME _convert_DELETE_GLOBAL = _convert_NAME @@ -766,7 +769,9 @@ 'BUILD_CLASS': -2, 'STORE_NAME': -1, 'STORE_ATTR': -2, + 'STORE_VIEWATTR': -2, 'DELETE_ATTR': -1, + 'DELETE_VIEWATTR': -1, 'STORE_GLOBAL': -1, 'BUILD_MAP': 1, 'COMPARE_OP': -1, @@ -775,6 +780,7 @@ 'IMPORT_NAME': -1, 'IMPORT_FROM': 1, 'LOAD_ATTR': 0, # unlike other loads + 'LOAD_VIEWATTR': 0, # close enough... 'SETUP_EXCEPT': 3, 'SETUP_FINALLY': 3, Index: Lib/opcode.py =================================================================== --- Lib/opcode.py (revision 60424) +++ Lib/opcode.py (working copy) @@ -145,12 +145,13 @@ hascompare.append(106) name_op('IMPORT_NAME', 107) # Index in name list name_op('IMPORT_FROM', 108) # Index in name list - +name_op('LOAD_VIEWATTR', 109) # Index in name list jrel_op('JUMP_FORWARD', 110) # Number of bytes to skip jrel_op('JUMP_IF_FALSE', 111) # "" jrel_op('JUMP_IF_TRUE', 112) # "" jabs_op('JUMP_ABSOLUTE', 113) # Target byte offset from beginning of code - +name_op('STORE_VIEWATTR', 114) # Index in name list +name_op('DELETE_VIEWATTR', 115) # Index in name list name_op('LOAD_GLOBAL', 116) # Index in name list jabs_op('CONTINUE_LOOP', 119) # Target address Index: Lib/test/test_dictviews.py =================================================================== --- Lib/test/test_dictviews.py (revision 0) +++ Lib/test/test_dictviews.py (revision 0) @@ -0,0 +1,77 @@ +from __future__ import dict_views +import unittest +from test import test_support + +class DictSetTest(unittest.TestCase): + + def test_constructors_not_callable(self): + kt = type({}.keys()) + self.assertRaises(TypeError, kt, {}) + self.assertRaises(TypeError, kt) + it = type({}.items()) + self.assertRaises(TypeError, it, {}) + self.assertRaises(TypeError, it) + vt = type({}.values()) + self.assertRaises(TypeError, vt, {}) + self.assertRaises(TypeError, vt) + + def test_dict_keys(self): + d = {1: 10, "a": "ABC"} + keys = d.keys() + 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.keys(), d.keys()) + e = {1: 11, "a": "def"} + self.assertEqual(d.keys(), e.keys()) + del e["a"] + self.assertNotEqual(d.keys(), e.keys()) + + def test_dict_items(self): + d = {1: 10, "a": "ABC"} + items = d.items() + 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.items(), d.items()) + e = d.copy() + self.assertEqual(d.items(), e.items()) + e["a"] = "def" + self.assertNotEqual(d.items(), e.items()) + + def test_dict_mixed_keys_items(self): + d = {(1, 1): 11, (2, 2): 22} + e = {1: 1, 2: 2} + self.assertEqual(d.keys(), e.items()) + self.assertNotEqual(d.items(), e.keys()) + + def test_dict_values(self): + d = {1: 10, "a": "ABC"} + values = d.values() + 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() Index: Lib/test/test_dict.py =================================================================== --- Lib/test/test_dict.py (revision 60424) +++ Lib/test/test_dict.py (working copy) @@ -35,6 +35,18 @@ self.assertRaises(TypeError, d.keys, None) + def test_viewkeys(self): + d = {} + self.assertEqual(d.viewkeys(), set()) + d = {'a': 1, 'b': 2} + k = d.viewkeys() + self.assert_('a' in k) + self.assert_('b' in k) + self.assertFalse('c' in k) + self.assertEquals(k, set(['a', 'b'])) + + self.assertRaises(TypeError, d.viewkeys, None) + def test_values(self): d = {} self.assertEqual(d.values(), []) @@ -43,15 +55,32 @@ self.assertRaises(TypeError, d.values, None) + def test_viewvalues(self): + d = {} + self.assertEqual(list(d.viewvalues()), []) + d = {1:2} + self.assertEqual(list(d.viewvalues()), [2]) + + self.assertRaises(TypeError, d.viewvalues, None) + def test_items(self): d = {} - self.assertEqual(d.items(), []) + self.assertEqual(list(d.items()), []) d = {1:2} self.assertEqual(d.items(), [(1, 2)]) self.assertRaises(TypeError, d.items, None) + def test_viewitems(self): + d = {} + self.assertEqual(list(d.viewitems()), []) + + d = {1:2} + self.assertEqual(list(d.viewitems()), [(1, 2)]) + + self.assertRaises(TypeError, d.viewitems, None) + def test_has_key(self): d = {} self.assert_(not d.has_key('a')) @@ -542,7 +571,90 @@ resizing = True d[9] = 6 + def test_dict_views_future(self): + import __future__ + d = {'a': 1, 'b': 2, 'c': 3} + c = compile("d.keys", "d.keys", "eval", 0) + k = eval(c, locals()) + self.assertEquals(k.__name__, d.keys.__name__) + c = compile("d.items", "d.items", "eval", 0) + k = eval(c, locals()) + self.assertEquals(k.__name__, d.items.__name__) + c = compile("d.values", "d.values", "eval", 0) + k = eval(c, locals()) + self.assertEquals(k.__name__, d.values.__name__) + c = compile("d.keys", "d.keys", "eval", + __future__.CO_FUTURE_DICT_VIEWS) + k = eval(c, locals()) + self.assertEquals(k.__name__, d.viewkeys.__name__) + c = compile("d.items", "d.items", "eval", + __future__.CO_FUTURE_DICT_VIEWS) + k = eval(c, locals()) + self.assertEquals(k.__name__, d.viewitems.__name__) + c = compile("d.values", "d.values", "eval", + __future__.CO_FUTURE_DICT_VIEWS) + k = eval(c, locals()) + self.assertEquals(k.__name__, d.viewvalues.__name__) + + d = Dict(a=1, b=2, c=3) + c = compile("d.keys", "d.keys", "eval", 0) + k = eval(c, locals()) + self.assertEquals(k.__name__, d.keys.__name__) + c = compile("d.items", "d.items", "eval", 0) + k = eval(c, locals()) + self.assertEquals(k.__name__, d.items.__name__) + c = compile("d.values", "d.values", "eval", 0) + k = eval(c, locals()) + self.assertEquals(k.__name__, d.values.__name__) + c = compile("d.keys", "d.keys", "eval", + __future__.CO_FUTURE_DICT_VIEWS) + k = eval(c, locals()) + self.assertEquals(k.__name__, d.viewkeys.__name__) + c = compile("d.items", "d.items", "eval", + __future__.CO_FUTURE_DICT_VIEWS) + k = eval(c, locals()) + self.assertEquals(k.__name__, d.viewitems.__name__) + c = compile("d.values", "d.values", "eval", + __future__.CO_FUTURE_DICT_VIEWS) + k = eval(c, locals()) + self.assertEquals(k.__name__, d.viewvalues.__name__) + + class NonDict: + def __init__(self, *args, **kwargs): + self.d = dict(*args, **kwargs) + def keys(self): return self.d.keys() + def items(self): return self.d.items() + def values(self): return self.d.values() + # these should *not* end up being used + def viewkeys(self): pass + def viewitems(self): pass + def viewvalues(self): pass + + d = NonDict(a=1, b=2, c=3) + c = compile("d.keys", "d.keys", "eval", 0) + k = eval(c, locals()) + self.assertEquals(k.__name__, d.keys.__name__) + c = compile("d.items", "d.items", "eval", 0) + k = eval(c, locals()) + self.assertEquals(k.__name__, d.items.__name__) + c = compile("d.values", "d.values", "eval", 0) + k = eval(c, locals()) + self.assertEquals(k.__name__, d.values.__name__) + c = compile("d.keys", "d.keys", "eval", + __future__.CO_FUTURE_DICT_VIEWS) + k = eval(c, locals()) + self.assertEquals(k.__name__, d.keys.__name__) + c = compile("d.items", "d.items", "eval", + __future__.CO_FUTURE_DICT_VIEWS) + k = eval(c, locals()) + self.assertEquals(k.__name__, d.items.__name__) + c = compile("d.values", "d.values", "eval", + __future__.CO_FUTURE_DICT_VIEWS) + k = eval(c, locals()) + self.assertEquals(k.__name__, d.values.__name__) + + from test import mapping_tests class GeneralMappingTests(mapping_tests.BasicTestMappingProtocol): Index: Lib/test/test_compiler.py =================================================================== --- Lib/test/test_compiler.py (revision 60424) +++ Lib/test/test_compiler.py (working copy) @@ -180,7 +180,41 @@ # ShiftJIS source without encdef self._testErrEnc(sjis, sjis, 19) + def testDictViews(self): + class Dict(dict): pass + class NonDict: + def keys(self): pass + def items(self): pass + def values(self): pass + def viewkeys(self): pass + def viewitems(self): pass + def viewvalues(self): pass + c = compiler.compile('from __future__ import dict_views\n' + 'keys, items, values = d.keys, d.items, d.values\n', + '', + 'exec') + obj = {} + dct = {'d': obj} + exec c in dct + self.assertEquals(dct['keys'].__name__, obj.viewkeys.__name__) + self.assertEquals(dct['items'].__name__, obj.viewitems.__name__) + self.assertEquals(dct['values'].__name__, obj.viewvalues.__name__) + obj = Dict() + dct = {'d': obj} + exec c in dct + self.assertEquals(dct['keys'].__name__, obj.viewkeys.__name__) + self.assertEquals(dct['items'].__name__, obj.viewitems.__name__) + self.assertEquals(dct['values'].__name__, obj.viewvalues.__name__) + + obj = NonDict() + dct = {'d': obj} + exec c in dct + self.assertEquals(dct['keys'].__name__, obj.keys.__name__) + self.assertEquals(dct['items'].__name__, obj.items.__name__) + self.assertEquals(dct['values'].__name__, obj.values.__name__) + + NOLINENO = (compiler.ast.Module, compiler.ast.Stmt, compiler.ast.Discard) ###############################################################################