Skip to content

Commit fdef5a2

Browse files
committed
WIP on sq_ass_item
1 parent 5978249 commit fdef5a2

File tree

3 files changed

+222
-1
lines changed

3 files changed

+222
-1
lines changed

doc/sphinx/source/new_types.rst

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -913,6 +913,112 @@ Tests are in ``tests/unit/test_c_seqobject.py`` which includes failure modes:
913913
assert err.value.args[0] == expected
914914
915915
916+
---------------
917+
``sq_ass_item``
918+
---------------
919+
920+
`sq_ass_item`_ gives write and delete access to an indexed member.
921+
922+
.. list-table:: Sequence Methods: ``sq_ass_item``
923+
:widths: 20 80
924+
:header-rows: 0
925+
926+
* - Member
927+
- `sq_ass_item`_
928+
* - Function type
929+
- `ssizeobjargproc`_
930+
* - Function signature
931+
- ``int (*ssizeobjargproc)(PyObject*, Py_ssize_t, PyObject*)``
932+
* - Description
933+
- Sets the the n'th item in the sequence.
934+
If the value is NULL the item is deleted and the sequence concatenated (thus called by `PyObject_DelItem()`_).
935+
Negative indexes are handled appropriately.
936+
Used by `PyObject_SetItem()`_.
937+
938+
Implementation
939+
--------------
940+
941+
In ``src/cpy/Object/cSeqObject.c``:
942+
943+
.. code-block:: c
944+
945+
/** Returns a new reference to an indexed item in a sequence. */
946+
static PyObject *
947+
SequenceLongObject_sq_item(PyObject *self, Py_ssize_t index) {
948+
Py_ssize_t my_index = index;
949+
if (my_index < 0) {
950+
my_index += SequenceLongObject_sq_length(self);
951+
}
952+
// Corner case example: len(self) == 0 and index < 0
953+
if (my_index < 0 || my_index >= SequenceLongObject_sq_length(self)) {
954+
PyErr_Format(
955+
PyExc_IndexError,
956+
"Index %ld is out of range for length %ld",
957+
index,
958+
SequenceLongObject_sq_length(self)
959+
);
960+
return NULL;
961+
}
962+
return PyLong_FromLong(((SequenceLongObject *) self)->array_long[my_index]);
963+
}
964+
965+
Tests
966+
--------------
967+
968+
Tests are in ``tests/unit/test_c_seqobject.py`` which includes failure modes:
969+
970+
.. code-block:: python
971+
972+
from cPyExtPatt import cSeqObject
973+
974+
@pytest.mark.parametrize(
975+
'initial_sequence, index, expected',
976+
(
977+
(
978+
[7, 4, 1, ], 0, 7,
979+
),
980+
(
981+
[7, 4, 1, ], 1, 4,
982+
),
983+
(
984+
[7, 4, 1, ], 2, 1,
985+
),
986+
(
987+
[7, 4, 1, ], -1, 1,
988+
),
989+
(
990+
[7, 4, 1, ], -2, 4,
991+
),
992+
(
993+
[7, 4, 1, ], -3, 7,
994+
),
995+
)
996+
)
997+
def test_SequenceLongObject_item(initial_sequence, index, expected):
998+
obj = cSeqObject.SequenceLongObject(initial_sequence)
999+
assert obj[index] == expected
1000+
1001+
@pytest.mark.parametrize(
1002+
'initial_sequence, index, expected',
1003+
(
1004+
(
1005+
[], 0, 'Index 0 is out of range for length 0',
1006+
),
1007+
(
1008+
[], -1, 'Index -1 is out of range for length 0',
1009+
),
1010+
(
1011+
[1, ], 2, 'Index 2 is out of range for length 1',
1012+
),
1013+
)
1014+
)
1015+
def test_SequenceLongObject_item_raises(initial_sequence, index, expected):
1016+
obj = cSeqObject.SequenceLongObject(initial_sequence)
1017+
with pytest.raises(IndexError) as err:
1018+
obj[index]
1019+
assert err.value.args[0] == expected
1020+
1021+
9161022
9171023
9181024

src/cpy/Object/cSeqObject.c

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,13 +215,96 @@ SequenceLongObject_sq_item(PyObject *self, Py_ssize_t index) {
215215
}
216216
return PyLong_FromLong(((SequenceLongObject *) self)->array_long[my_index]);
217217
}
218+
static int
219+
SequenceLongObject_sq_ass_item(PyObject *self, Py_ssize_t index, PyObject *value) {
220+
Py_ssize_t my_index = index;
221+
if (my_index < 0) {
222+
my_index += SequenceLongObject_sq_length(self);
223+
}
224+
// Corner case example: len(self) == 0 and index < 0
225+
if (my_index < 0 || my_index >= SequenceLongObject_sq_length(self)) {
226+
PyErr_Format(
227+
PyExc_IndexError,
228+
"Index %ld is out of range for length %ld",
229+
index,
230+
SequenceLongObject_sq_length(self)
231+
);
232+
return -1;
233+
}
234+
if (value != NULL) {
235+
/* Just set the value. */
236+
if (!PyLong_Check(value)) {
237+
PyErr_Format(
238+
PyExc_TypeError,
239+
"sq_ass_item value needs to be an int, not type %s",
240+
Py_TYPE(value)->tp_name
241+
);
242+
return -1;
243+
}
244+
((SequenceLongObject *) self)->array_long[my_index] = PyLong_AsLong(value);
245+
} else {
246+
/* Delete the value. */
247+
/* For convenience. */
248+
SequenceLongObject *self_as_slo = (SequenceLongObject *) self;
249+
/* Special case: deleting the only item in the array. */
250+
if (self_as_slo->size == 1) {
251+
free(self_as_slo->array_long);
252+
self_as_slo->array_long = NULL;
253+
self_as_slo->size = 0;
254+
} else {
255+
/* Delete the value and re-compose the array. */
256+
long *new_array = malloc((self_as_slo->size - 1) * sizeof(long));
257+
if (!new_array) {
258+
PyErr_Format(
259+
PyExc_MemoryError,
260+
"sq_ass_item can not allocate new array. %s#%d",
261+
__FILE__, __LINE__
262+
);
263+
return -1;
264+
}
265+
/* memcpy across to the new array, firstly up to the index. */
266+
void *dest = NULL;
267+
void *src = NULL;
268+
size_t count = 0;
269+
270+
dest = new_array;
271+
src = self_as_slo->array_long;
272+
count = my_index * sizeof(long);
273+
if (memcpy(dest, src, count) != dest) {
274+
PyErr_Format(
275+
PyExc_MemoryError,
276+
"sq_ass_item can not memcpy into new array. %s#%d",
277+
__FILE__, __LINE__
278+
);
279+
return -1;
280+
}
281+
/* memcpy across to the new array, from the index to the end. */
282+
dest = new_array + count;
283+
src = self_as_slo->array_long + (count - sizeof(long));
284+
/* Example size=4, index=2 copy one value */
285+
count = (self_as_slo->size - my_index - 1) * sizeof(long);
286+
if (memcpy(dest, src, count) != dest) {
287+
PyErr_Format(
288+
PyExc_MemoryError,
289+
"sq_ass_item can not memcpy into new array. %s#%d",
290+
__FILE__, __LINE__
291+
);
292+
return -1;
293+
}
294+
free(self_as_slo->array_long);
295+
self_as_slo->array_long = new_array;
296+
--self_as_slo->size;
297+
}
298+
}
299+
return 0;
300+
}
218301

219302
PySequenceMethods SequenceLongObject_sequence_methods = {
220303
.sq_length = &SequenceLongObject_sq_length,
221304
.sq_concat = &SequenceLongObject_sq_concat,
222305
.sq_repeat = &SequenceLongObject_sq_repeat,
223306
.sq_item = &SequenceLongObject_sq_item,
224-
.sq_ass_item = NULL,
307+
.sq_ass_item = &SequenceLongObject_sq_ass_item,
225308
.sq_contains = NULL,
226309
.sq_inplace_concat = NULL,
227310
.sq_inplace_repeat = NULL,

tests/unit/test_c_seqobject.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ def test_SequenceLongObject_dir():
1616
'__add__',
1717
'__class__',
1818
'__delattr__',
19+
'__delitem__',
1920
'__dir__',
2021
'__doc__',
2122
'__eq__',
@@ -39,6 +40,7 @@ def test_SequenceLongObject_dir():
3940
'__repr__',
4041
'__rmul__',
4142
'__setattr__',
43+
'__setitem__',
4244
'__sizeof__',
4345
'__str__',
4446
'__subclasshook__',
@@ -144,6 +146,36 @@ def test_SequenceLongObject_item_raises(initial_sequence, index, expected):
144146
obj[index]
145147
assert err.value.args[0] == expected
146148

149+
150+
@pytest.mark.parametrize(
151+
'initial_sequence, index, value, expected',
152+
(
153+
(
154+
[7, 4, 1, ], 0, 14, [14, 4, 1, ],
155+
),
156+
(
157+
[7, 4, 1, ], -1, 14, [7, 4, 14, ],
158+
),
159+
(
160+
[7,], 0, None, [],
161+
),
162+
(
163+
[7,], -1, None, [],
164+
),
165+
(
166+
[7, 4, 1, ], 0, None, [4, 14, ],
167+
),
168+
)
169+
)
170+
def test_SequenceLongObject_setitem(initial_sequence, index, value, expected):
171+
obj = cSeqObject.SequenceLongObject(initial_sequence)
172+
if value is not None:
173+
obj[index] = value
174+
else:
175+
del obj[index]
176+
assert list(obj) == expected
177+
178+
147179
# @pytest.mark.skipif(not (sys.version_info.minor < 7), reason='Python < 3.7')
148180
# def test_str_dir_pre_37():
149181
# s = cObject.Str()

0 commit comments

Comments
 (0)