Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
6af4757
Add support of PyStructSequence in copy.replace()
XuehaiPan Oct 2, 2023
1960d8d
Correct error message for `namedtuple._replace`
XuehaiPan Oct 2, 2023
5ee97a8
📜🤖 Added by blurb_it.
blurb-it[bot] Oct 2, 2023
e8e6be4
Add test for `copy.replace()` for struct sequence objects
XuehaiPan Oct 2, 2023
43324a4
Move test for `copy.replace(structseq)` to test_structseq.py
XuehaiPan Oct 2, 2023
9e6cd98
Fix refcnt for temp variables
XuehaiPan Oct 2, 2023
350a3d0
Change copy.replace() to raise TypeError for unkown fields for named …
XuehaiPan Oct 2, 2023
c4655ca
Make copy.replace() to raise TypeError for PyStructSequence with unna…
XuehaiPan Oct 2, 2023
a9dba93
Revert copy.replace() to raise ValueError for unkown fields for named…
XuehaiPan Oct 2, 2023
83fd01b
Change comment in tests
XuehaiPan Oct 2, 2023
73e5e65
Add more tests for PyStructSequence with invisible fields
XuehaiPan Oct 2, 2023
7abaf64
Change test function names to match code style
XuehaiPan Oct 2, 2023
26eedbc
Refactor implementation with copy and replacement
XuehaiPan Oct 2, 2023
d74b449
Fix missing assertHasAttr
XuehaiPan Oct 2, 2023
5186c35
Fix test cases
XuehaiPan Oct 2, 2023
0f74efa
Fix error handling
XuehaiPan Oct 2, 2023
43a81b0
Remove unused variable
XuehaiPan Oct 2, 2023
d90e5ae
Cast return type to `PyObject*`
XuehaiPan Oct 2, 2023
287f8b0
Merge branch 'main' into copy-replace-structseq
XuehaiPan Oct 2, 2023
5722773
Update news entry
XuehaiPan Oct 2, 2023
c23165b
Prefer ++i over i++
XuehaiPan Oct 2, 2023
38f331b
Limit the length of typename to 500 in error message
XuehaiPan Oct 3, 2023
30758ba
Merge branch 'main' into copy-replace-structseq
XuehaiPan Oct 3, 2023
2a6259c
Prefer PyDict_GET_SIZE over PyDict_Size
XuehaiPan Oct 3, 2023
a6b0292
Apply suggestions from code review
XuehaiPan Oct 3, 2023
d76bb4e
Apply suggestions from code review
XuehaiPan Oct 3, 2023
3ae391f
Update test code style
XuehaiPan Oct 3, 2023
3664109
Merge branch 'main' into copy-replace-structseq
XuehaiPan Oct 3, 2023
4a606ad
Apply suggestions from code review
serhiy-storchaka Oct 3, 2023
8504345
Merge branch 'main' into copy-replace-structseq
XuehaiPan Oct 3, 2023
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
2 changes: 1 addition & 1 deletion Lib/collections/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ def _make(cls, iterable):
def _replace(self, /, **kwds):
result = self._make(_map(kwds.pop, field_names, self))
if kwds:
raise ValueError(f'Got unexpected field names: {list(kwds)!r}')
raise ValueError(f'Got unexpected field name(s): {list(kwds)!r}')
Comment thread
XuehaiPan marked this conversation as resolved.
Outdated
return result

_replace.__doc__ = (f'Return a new {typename} object replacing specified '
Expand Down
24 changes: 24 additions & 0 deletions Lib/test/test_copy.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import copyreg
import weakref
import abc
import os
import time
from operator import le, lt, ge, gt, eq, ne, attrgetter

import unittest
Expand Down Expand Up @@ -945,6 +947,28 @@ def test_namedtuple(self):
with self.assertRaisesRegex(ValueError, 'unexpected field name'):
copy.replace(p, x=1, error=2)

def test_structseq(self):
Comment thread
XuehaiPan marked this conversation as resolved.
Outdated
t = time.gmtime(0)
self.assertEqual(copy.replace(t), (1970, 1, 1, 0, 0, 0, 3, 1, 0))
self.assertEqual(copy.replace(t, tm_year=2000),
(2000, 1, 1, 0, 0, 0, 3, 1, 0))
self.assertEqual(copy.replace(t, tm_mon=2),
(1970, 2, 1, 0, 0, 0, 3, 1, 0))
self.assertEqual(copy.replace(t, tm_year=2000, tm_mon=2),
(2000, 2, 1, 0, 0, 0, 3, 1, 0))
with self.assertRaisesRegex(ValueError, 'unexpected field name'):
copy.replace(t, tm_year=2000, error=2)

assert os.stat_result.n_unnamed_fields > 0
r = os.stat_result(range(os.stat_result.n_sequence_fields), {'st_atime_ns': -1})
self.assertHasAttr(r, 'st_atime_ns')
self.assertEqual(r.st_atime_ns, -1)
with self.assertRaisesRegex(AttributeError, 'readonly attribute'):
r.st_atime_ns = -2
r2 = copy.replace(r, st_atime_ns=-3)
self.assertHasAttr(r2, 'st_atime_ns')
self.assertEqual(r2.st_atime_ns, -3)

def test_dataclass(self):
from dataclasses import dataclass
@dataclass
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support of struct sequence objects in :func:`copy.replace`.
93 changes: 92 additions & 1 deletion Objects/structseq.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

#include "Python.h"
#include "pycore_dict.h" // _PyDict_Pop()
#include "pycore_tuple.h" // _PyTuple_FromArray()
#include "pycore_object.h" // _PyObject_GC_TRACK()

Expand Down Expand Up @@ -365,9 +366,99 @@ structseq_reduce(PyStructSequence* self, PyObject *Py_UNUSED(ignored))
return NULL;
}


static PyObject *
structseq_replace(PyStructSequence *self, PyObject *args, PyObject *kwargs)
{
PyObject *tup = NULL;
PyObject *dict = NULL;
PyObject *result;
Py_ssize_t n_fields, n_visible_fields, n_unnamed_fields, i;

if (!_PyArg_NoPositional("__replace__", args)) {
return NULL;
}

n_fields = REAL_SIZE(self);
if (n_fields < 0) {
return NULL;
}
n_visible_fields = VISIBLE_SIZE(self);
n_unnamed_fields = UNNAMED_FIELDS(self);
if (n_unnamed_fields < 0) {
return NULL;
}
tup = _PyTuple_FromArray(self->ob_item, n_visible_fields);
if (!tup) {
goto error;
}

dict = PyDict_New();
if (!dict) {
goto error;
Comment thread
XuehaiPan marked this conversation as resolved.
Outdated
}

if (kwargs != NULL) {
for (i = 0; i < n_visible_fields; i++) {
const char *name = Py_TYPE(self)->tp_members[i].name;
PyObject *key = PyUnicode_FromString(name);
if (!key) {
goto error;
}
PyObject *ob = _PyDict_Pop(kwargs, key, self->ob_item[i]);
Py_DECREF(key);
if (!ob) {
goto error;
}
PyTuple_SetItem(tup, i, ob);
}
for (i = n_visible_fields; i < n_fields; i++) {
const char *name = Py_TYPE(self)->tp_members[i - n_unnamed_fields].name;
PyObject *key = PyUnicode_FromString(name);
if (!key) {
goto error;
}
PyObject *ob = _PyDict_Pop(kwargs, key, self->ob_item[i]);
if (PyDict_SetItem(dict, key, ob) < 0) {
goto error;
}
}
if (PyDict_Size(kwargs) > 0) {
PyObject *names = PyDict_Keys(kwargs);
if (names) {
PyErr_Format(PyExc_ValueError, "Got unexpected field name(s): %R", names);
Comment thread
XuehaiPan marked this conversation as resolved.
Outdated
Py_DECREF(names);
}
goto error;
}
}
else
{
for (i = n_visible_fields; i < n_fields; i++) {
const char *name = Py_TYPE(self)->tp_members[i - n_unnamed_fields].name;
if (PyDict_SetItemString(dict, name, self->ob_item[i]) < 0) {
goto error;
}
}
}

result = structseq_new_impl(Py_TYPE(self), tup, dict);

Py_DECREF(tup);
Py_DECREF(dict);

return result;

error:
Py_XDECREF(tup);
Py_XDECREF(dict);
return NULL;
}

static PyMethodDef structseq_methods[] = {
{"__reduce__", (PyCFunction)structseq_reduce, METH_NOARGS, NULL},
{NULL, NULL}
{"__replace__", _PyCFunction_CAST(structseq_replace), METH_VARARGS | METH_KEYWORDS, NULL},
{NULL, NULL} // sentinel
};

static Py_ssize_t
Expand Down