diff -r 99640494ca7f Lib/test/test_structseq.py --- a/Lib/test/test_structseq.py Tue Nov 05 21:55:46 2013 -0600 +++ b/Lib/test/test_structseq.py Thu Nov 07 16:10:03 2013 +0530 @@ -43,6 +43,31 @@ self.assertIn("st_ino=", rep) self.assertIn("st_dev=", rep) + def test_eval_repr(self): + # Issue 5907 + t = time.gmtime() + self.assertEqual(eval(repr(t)), t) + + def test_replace(self): + # Issue 1820: Match namedtuple API + t1 = time.gmtime(0) + t2 = t1._replace(tm_min = 42, tm_zone = 'IST') + self.assertEqual(t2.tm_year, 1970) + self.assertEqual(t2.tm_min, 42) + self.assertEqual(t2.tm_zone, 'IST') + + self.assertRaises(TypeError, t2._replace, 42) + self.assertRaises(ValueError, t2._replace, tm_nothere = 42) + + def test_asdict(self): + # Issue 1820: Match namedtuple API + d = {'tm_yday': 1, 'tm_sec': 0, 'tm_mon': 1, 'tm_gmtoff': 0, 'tm_zone': 'GMT', + 'tm_year': 1970, 'tm_min': 0, 'tm_isdst': 0, 'tm_wday': 3, + 'tm_mday': 1, 'tm_hour': 0} + + t = time.gmtime(0) + self.assertEqual(t._asdict(), d) + def test_concat(self): t1 = time.gmtime() t2 = t1 + tuple(t1) @@ -81,6 +106,10 @@ self.assertEqual(len(t), t.n_sequence_fields) self.assertEqual(t.n_unnamed_fields, 0) self.assertEqual(t.n_fields, time._STRUCT_TM_ITEMS) + # Issue 1820: Match namedtuple API + fields = ('tm_year', 'tm_mon', 'tm_mday', 'tm_hour', 'tm_min', + 'tm_sec', 'tm_wday', 'tm_yday', 'tm_isdst') + self.assertEqual(t._fields, fields) def test_constructor(self): t = time.struct_time diff -r 99640494ca7f Objects/structseq.c --- a/Objects/structseq.c Tue Nov 05 21:55:46 2013 -0600 +++ b/Objects/structseq.c Thu Nov 07 16:10:03 2013 +0530 @@ -77,10 +77,45 @@ PyObject *dict = NULL; PyObject *ob; PyStructSequence *res = NULL; - Py_ssize_t len, min_len, max_len, i, n_unnamed_fields; + Py_ssize_t len, i; + Py_ssize_t n_args = 0, n_kwds = 0; + Py_ssize_t min_len = VISIBLE_SIZE_TP(type); + Py_ssize_t max_len = REAL_SIZE_TP(type); + Py_ssize_t n_unnamed_fields = UNNAMED_FIELDS_TP(type); static char *kwlist[] = {"sequence", "dict", 0}; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O:structseq", + if(args != NULL) + n_args = PyTuple_GET_SIZE(args); + + /* Only keyword arguments have been specified Eg: eval(repr(obj))*/ + if (!n_args && kwds != NULL && PyArg_ValidateKeywordArguments(kwds) && + !PyDict_GetItemString(kwds, "sequence")) { + n_kwds = PyDict_Size(kwds); + if ((dict = PyDict_GetItemString(kwds, "dict"))) + n_kwds--; + if (n_kwds != min_len) { + PyErr_Format(PyExc_TypeError, + "%.500s() requires %zd keyword arguments and " + "can take an optional 'dict' argument (%zd given)", + type->tp_name, min_len, n_kwds); + return NULL; + } + arg = PyTuple_New(min_len); + for (i = 0; i < min_len; i++) { + PyObject *v = PyDict_GetItemString(kwds, type->tp_members[i].name); + if(!v) { + PyErr_Format(PyExc_TypeError, + "%.500s() constructor requires key '%s'", + type->tp_name, type->tp_members[i].name); + Py_DECREF(arg); + return NULL; + } + Py_INCREF(v); + PyTuple_SET_ITEM(arg, i, v); + } + } + + else if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O:structseq", kwlist, &arg, &dict)) return NULL; @@ -99,9 +134,6 @@ } len = PySequence_Fast_GET_SIZE(arg); - min_len = VISIBLE_SIZE_TP(type); - max_len = REAL_SIZE_TP(type); - n_unnamed_fields = UNNAMED_FIELDS_TP(type); if (min_len != max_len) { if (len < min_len) { @@ -274,8 +306,115 @@ return NULL; } +static PyStructSequence * +structseq_replace(PyStructSequence *self, PyObject *args, PyObject *kwds) { + PyStructSequence *res; + PyObject *val; + Py_ssize_t i; + PyTypeObject *type = Py_TYPE(self); + Py_ssize_t n_fields = REAL_SIZE(self); + Py_ssize_t n_visible_fields = VISIBLE_SIZE(self); + Py_ssize_t n_unnamed_fields = UNNAMED_FIELDS(self); + Py_ssize_t n_args = 0, n_kwds = 0; + + if (args != NULL) + n_args = PyTuple_GET_SIZE(args); + + if (kwds != NULL) + n_kwds = PyDict_Size(kwds); + + if (n_args) { + PyErr_Format(PyExc_TypeError, + "_replace() takes 1 positional argument " + "but %zd were given", n_args + 1); + return NULL; + } + + if (n_kwds == -1 || kwds == NULL) { + PyErr_Format(PyExc_TypeError, + "_replace() takes at least one keyword argument"); + return NULL; + } + + if (!PyArg_ValidateKeywordArguments(kwds)) { + PyErr_Format(PyExc_TypeError, + "_replace() takes at least one keyword argument"); + return NULL; + } + + res = (PyStructSequence *) PyStructSequence_New(type); + + if (res == NULL) { + return NULL; + } + +#define SET_NEW_STRUCTSEQ_ITEM(key, i) \ + do { \ + if(key && (val = PyDict_GetItemString(kwds, key))) \ + PyDict_DelItemString(kwds, key); \ + else \ + val = PyStructSequence_GET_ITEM(self, i); \ + Py_INCREF(val); \ + PyStructSequence_SET_ITEM(res, i, val); \ + } while(0) + + for (i = 0; i < n_visible_fields - n_unnamed_fields; i++) + SET_NEW_STRUCTSEQ_ITEM(type->tp_members[i].name, i); + + for (; i < n_visible_fields; i++) + SET_NEW_STRUCTSEQ_ITEM(NULL, i); + + for (; i < n_fields; i++) + SET_NEW_STRUCTSEQ_ITEM(type->tp_members[i - n_unnamed_fields].name, i); + + n_kwds = PyDict_Size(kwds); + + if(n_kwds > 0) { + PyErr_Format(PyExc_ValueError, + "Got unexpected field names: %s", + _PyUnicode_AsString(PyObject_Repr(PyDict_Keys(kwds)))); + return NULL; + } + + Py_INCREF(res); + return res; +} + +static PyObject * +structseq_asdict(PyStructSequence *self) { + PyObject *dict = NULL; + Py_ssize_t i; + PyTypeObject *type = Py_TYPE(self); + Py_ssize_t n_fields = REAL_SIZE(self); + Py_ssize_t n_visible_fields = VISIBLE_SIZE(self); + Py_ssize_t n_unnamed_fields = UNNAMED_FIELDS(self); + dict = PyDict_New(); + if (!dict) + goto error; + +#define SET_STRUCTSEQ_DICT(key, i) \ + do { \ + Py_INCREF(self->ob_item[i]); \ + if (PyDict_SetItemString(dict, key, self->ob_item[i]) < 0) \ + goto error; \ + } while(0) + + for (i = 0; i < n_visible_fields - n_unnamed_fields; i++) + SET_STRUCTSEQ_DICT(type->tp_members[i].name, i); + + for (i = n_visible_fields; i < n_fields; i++) + SET_STRUCTSEQ_DICT(type->tp_members[i - n_unnamed_fields].name, i); + + return dict; +error: + Py_XDECREF(dict); + return NULL; +} + static PyMethodDef structseq_methods[] = { {"__reduce__", (PyCFunction)structseq_reduce, METH_NOARGS, NULL}, + {"_replace", (PyCFunction)structseq_replace, METH_VARARGS | METH_KEYWORDS, NULL}, + {"_asdict", (PyCFunction)structseq_asdict, METH_NOARGS, NULL}, {NULL, NULL} }; @@ -326,7 +465,7 @@ PyObject *dict; PyMemberDef* members; int n_members, n_unnamed_members, i, k; - PyObject *v; + PyObject *v, *_fields; #ifdef Py_TRACE_REFS /* if the type object was chained, unchain it first @@ -347,6 +486,13 @@ type->tp_name = desc->name; type->tp_doc = desc->doc; + /* _fields only has the visible named keys*/ + _fields = PyTuple_New(desc->n_in_sequence - n_unnamed_members); + if (_fields == NULL) { + PyErr_NoMemory(); + return -1; + } + members = PyMem_NEW(PyMemberDef, n_members-n_unnamed_members+1); if (members == NULL) { PyErr_NoMemory(); @@ -357,6 +503,12 @@ if (desc->fields[i].name == PyStructSequence_UnnamedField) continue; members[k].name = desc->fields[i].name; + if (i < desc->n_in_sequence) { + PyObject *field_name; + field_name = PyUnicode_FromString(members[k].name); + Py_INCREF(field_name); + PyTuple_SET_ITEM(_fields, k, field_name); + } members[k].type = T_OBJECT; members[k].offset = offsetof(PyStructSequence, ob_item) + i * sizeof(PyObject*); @@ -388,6 +540,7 @@ SET_DICT_FROM_INT(visible_length_key, desc->n_in_sequence); SET_DICT_FROM_INT(real_length_key, n_members); SET_DICT_FROM_INT(unnamed_fields_key, n_unnamed_members); + PyDict_SetItemString(dict, "_fields", _fields); return 0; }