Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 4 additions & 2 deletions Doc/c-api/tuple.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ Tuple Objects
Create a tuple of *size* items and copy references from *array* to the new
tuple.

* Return a new reference on success.
* Set an exception and return ``NULL`` on error.
*array* can be NULL if *size* is ``0``.

On success, return a new reference.
On error, set an exception and return ``NULL``.

.. versionadded:: next

Expand Down
34 changes: 22 additions & 12 deletions Lib/test/test_capi/test_tuple.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,28 @@ def test_tuple_new(self):
self.assertRaises(SystemError, tuple_new, PY_SSIZE_T_MIN)
self.assertRaises(MemoryError, tuple_new, PY_SSIZE_T_MAX)

def test_tuple_fromarray(self):
# Test PyTuple_FromArray()
tuple_fromarray = _testcapi.tuple_fromarray

tup = tuple([i] for i in range(5))
copy = tuple_fromarray(tup)
self.assertEqual(copy, tup)

tup = ()
copy = tuple_fromarray(tup)
self.assertIs(copy, tup)
Copy link
Member

Choose a reason for hiding this comment

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

Is not the empty tuple singleton a CPython implementation detail?

Copy link
Member Author

Choose a reason for hiding this comment

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

The whole file is a CPython implementation detail (_testcapi), no?


copy = tuple_fromarray(NULL, 0)
self.assertIs(copy, ())

with self.assertRaises(ValueError):
tuple_fromarray(NULL, -1)
with self.assertRaises(ValueError):
tuple_fromarray(NULL, PY_SSIZE_T_MIN)
with self.assertRaises(MemoryError):
tuple_fromarray(NULL, PY_SSIZE_T_MAX)

def test_tuple_pack(self):
# Test PyTuple_Pack()
pack = _testlimitedcapi.tuple_pack
Expand Down Expand Up @@ -279,18 +301,6 @@ def my_iter():
self.assertEqual(tuple(my_iter()), (TAG, *range(10)))
self.assertEqual(tuples, [])

def test_tuple_fromarray(self):
# Test PyTuple_FromArray()
tuple_fromarray = _testcapi.tuple_fromarray

tup = tuple(object() for _ in range(5))
copy = tuple_fromarray(tup)
self.assertEqual(copy, tup)

tup = ()
copy = tuple_fromarray(tup)
self.assertIs(copy, tup)


if __name__ == "__main__":
unittest.main()
40 changes: 13 additions & 27 deletions Modules/_testcapi/tuple.c
Original file line number Diff line number Diff line change
Expand Up @@ -108,40 +108,26 @@ static PyObject *
tuple_fromarray(PyObject* Py_UNUSED(module), PyObject *args)
{
PyObject *src;
if (!PyArg_ParseTuple(args, "O!", &PyTuple_Type, &src)) {
Py_ssize_t size = -1;
if (!PyArg_ParseTuple(args, "O|n", &src, &size)) {
return NULL;
}

Py_ssize_t size = PyTuple_GET_SIZE(src);
PyObject **array = PyMem_Malloc(size * sizeof(PyObject**));
if (array == NULL) {
PyErr_NoMemory();
if (src != Py_None && !PyTuple_Check(src)) {
PyErr_SetString(PyExc_TypeError, "expect a tuple");
return NULL;
}
for (Py_ssize_t i = 0; i < size; i++) {
array[i] = Py_NewRef(PyTuple_GET_ITEM(src, i));
}

PyObject *tuple = PyTuple_FromArray(array, size);
if (tuple == NULL) {
goto done;
}

for (Py_ssize_t i = 0; i < size; i++) {
// check that the array is not modified
assert(array[i] == PyTuple_GET_ITEM(src, i));

// check that the array was copied properly
assert(PyTuple_GET_ITEM(tuple, i) == array[i]);
PyObject **items;
if (src != Py_None) {
items = &PyTuple_GET_ITEM(src, 0);
if (size < 0) {
size = PyTuple_GET_SIZE(src);
}
}

done:
for (Py_ssize_t i = 0; i < size; i++) {
Py_DECREF(array[i]);
else {
items = NULL;
}
PyMem_Free(array);

return tuple;
return PyTuple_FromArray(items, size);
}


Expand Down
4 changes: 4 additions & 0 deletions Objects/tupleobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,10 @@ PyTuple_FromArray(PyObject *const *src, Py_ssize_t n)
if (n == 0) {
return tuple_get_empty();
}
if (n < 0) {
PyErr_SetString(PyExc_ValueError, "size must be nonnegative");
return NULL;
}

PyTupleObject *tuple = tuple_alloc(n);
if (tuple == NULL) {
Expand Down
Loading