Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions Lib/test/test_structseq.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,28 @@ def test_match_args_with_unnamed_fields(self):
self.assertEqual(os.stat_result.n_unnamed_fields, 3)
self.assertEqual(os.stat_result.__match_args__, expected_args)

def test_tuple_field_keys(self):
expected_keys = ('tm_year', 'tm_mon', 'tm_mday', 'tm_hour', 'tm_min',
'tm_sec', 'tm_wday', 'tm_yday', 'tm_isdst')
self.assertEqual(time.gmtime()._fields, time.struct_time._fields, expected_keys)

def test_tuple_field_defaults(self):
self.assertEqual(time.gmtime()._field_defaults, time.struct_time._field_defaults, {})

def test_tuple_asdict(self):
t = time.gmtime(0)
self.assertEqual(t._asdict(), {
'tm_year': 1970,
'tm_mon': 1,
'tm_mday': 1,
'tm_hour': 0,
'tm_min': 0,
'tm_sec': 0,
'tm_wday': 3,
'tm_yday': 1,
'tm_isdst': 0,
})


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add :attr:`_fields` and :attr:`_asdict` helpers to :c:type:`PyStructSequence`,
improving its compatibility with :class:`collections.namedtuple`.
48 changes: 46 additions & 2 deletions Objects/structseq.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ static const char visible_length_key[] = "n_sequence_fields";
static const char real_length_key[] = "n_fields";
static const char unnamed_fields_key[] = "n_unnamed_fields";
static const char match_args_key[] = "__match_args__";
static const char named_fields_list_key[] = "_fields";
static const char named_fields_defaults_key[] = "_field_defaults";

/* Fields with this name have only a field index, not a field name.
They are only allowed for indices < n_visible_fields. */
Expand Down Expand Up @@ -365,8 +367,35 @@ structseq_reduce(PyStructSequence* self, PyObject *Py_UNUSED(ignored))
return NULL;
}

static PyObject *
structseq_asdict(PyStructSequence* self, PyObject *Py_UNUSED(ignored))
{
PyObject* dict = NULL;
Py_ssize_t n_visible_fields, n_unnamed_fields, i;

n_visible_fields = VISIBLE_SIZE(self);
n_unnamed_fields = UNNAMED_FIELDS(self);

dict = PyDict_New();
if (!dict)
return NULL;

for (i = 0; i < n_visible_fields; i++) {
const char *n = Py_TYPE(self)->tp_members[i-n_unnamed_fields].name;
if (PyDict_SetItemString(dict, n, self->ob_item[i]) < 0)
goto error;
}

return dict;

error:
Py_DECREF(dict);
return NULL;
}

static PyMethodDef structseq_methods[] = {
{"__reduce__", (PyCFunction)structseq_reduce, METH_NOARGS, NULL},
{"_asdict", (PyCFunction)structseq_asdict, METH_NOARGS, NULL},
{NULL, NULL}
};

Expand All @@ -386,7 +415,7 @@ count_members(PyStructSequence_Desc *desc, Py_ssize_t *n_unnamed_members) {
static int
initialize_structseq_dict(PyStructSequence_Desc *desc, PyObject* dict,
Py_ssize_t n_members, Py_ssize_t n_unnamed_members) {
PyObject *v;
PyObject *v, *defaults = NULL;

#define SET_DICT_FROM_SIZE(key, value) \
do { \
Expand All @@ -405,7 +434,7 @@ initialize_structseq_dict(PyStructSequence_Desc *desc, PyObject* dict,
SET_DICT_FROM_SIZE(real_length_key, n_members);
SET_DICT_FROM_SIZE(unnamed_fields_key, n_unnamed_members);

// Prepare and set __match_args__
// Prepare and set __match_args__ and _fields
Py_ssize_t i, k;
PyObject* keys = PyTuple_New(desc->n_in_sequence);
if (keys == NULL) {
Expand All @@ -432,10 +461,25 @@ initialize_structseq_dict(PyStructSequence_Desc *desc, PyObject* dict,
goto error;
}

if (PyDict_SetItemString(dict, named_fields_list_key, keys) < 0) {
goto error;
}

// Set _fields_defaults to an empty dir, as we don't support defaults
defaults = PyDict_New();
if (!defaults)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (!defaults)
if (!defaults) {

nit

goto error;

if (PyDict_SetItemString(dict, named_fields_defaults_key, defaults) < 0) {
goto error;
}

Py_DECREF(defaults);
Py_DECREF(keys);
return 0;

error:
Py_XDECREF(defaults);
Py_DECREF(keys);
return -1;
}
Expand Down