Skip to content

Commit

Permalink
gh-121645: Add PyBytes_Join() function (#121646)
Browse files Browse the repository at this point in the history
* Replace _PyBytes_Join() with PyBytes_Join().
* Keep _PyBytes_Join() as an alias to PyBytes_Join().
  • Loading branch information
vstinner authored Aug 30, 2024
1 parent 7fca268 commit 3d60dfb
Show file tree
Hide file tree
Showing 10 changed files with 101 additions and 12 deletions.
18 changes: 18 additions & 0 deletions Doc/c-api/bytes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,24 @@ called with a non-bytes parameter.
to *newpart* (i.e. decrements its reference count).
.. c:function:: PyObject* PyBytes_Join(PyObject *sep, PyObject *iterable)
Similar to ``sep.join(iterable)`` in Python.
*sep* must be Python :class:`bytes` object.
(Note that :c:func:`PyUnicode_Join` accepts ``NULL`` separator and treats
it as a space, whereas :c:func:`PyBytes_Join` doesn't accept ``NULL``
separator.)
*iterable* must be an iterable object yielding objects that implement the
:ref:`buffer protocol <bufferobjects>`.
On success, return a new :class:`bytes` object.
On error, set an exception and return ``NULL``.
.. versionadded: 3.14
.. c:function:: int _PyBytes_Resize(PyObject **bytes, Py_ssize_t newsize)
Resize a bytes object. *newsize* will be the new length of the bytes object.
Expand Down
5 changes: 5 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,11 @@ New Features

(Contributed by Victor Stinner in :gh:`120389`.)

* Add :c:func:`PyBytes_Join(sep, iterable) <PyBytes_Join>` function,
similar to ``sep.join(iterable)`` in Python.
(Contributed by Victor Stinner in :gh:`121645`.)


Porting to Python 3.14
----------------------

Expand Down
7 changes: 4 additions & 3 deletions Include/cpython/bytesobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ static inline Py_ssize_t PyBytes_GET_SIZE(PyObject *op) {
}
#define PyBytes_GET_SIZE(self) PyBytes_GET_SIZE(_PyObject_CAST(self))

/* _PyBytes_Join(sep, x) is like sep.join(x). sep must be PyBytesObject*,
x must be an iterable object. */
PyAPI_FUNC(PyObject*) _PyBytes_Join(PyObject *sep, PyObject *x);
PyAPI_FUNC(PyObject*) PyBytes_Join(PyObject *sep, PyObject *iterable);

// Alias kept for backward compatibility
#define _PyBytes_Join PyBytes_Join
40 changes: 40 additions & 0 deletions Lib/test/test_capi/test_bytes.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,46 @@ def test_resize(self):
# CRASHES resize(NULL, 0, False)
# CRASHES resize(NULL, 3, False)

def test_join(self):
"""Test PyBytes_Join()"""
bytes_join = _testcapi.bytes_join

self.assertEqual(bytes_join(b'', []), b'')
self.assertEqual(bytes_join(b'sep', []), b'')

self.assertEqual(bytes_join(b'', [b'a', b'b', b'c']), b'abc')
self.assertEqual(bytes_join(b'-', [b'a', b'b', b'c']), b'a-b-c')
self.assertEqual(bytes_join(b' - ', [b'a', b'b', b'c']), b'a - b - c')
self.assertEqual(bytes_join(b'-', [bytearray(b'abc'),
memoryview(b'def')]),
b'abc-def')

self.assertEqual(bytes_join(b'-', iter([b'a', b'b', b'c'])), b'a-b-c')

# invalid 'sep' argument
with self.assertRaises(TypeError):
bytes_join(bytearray(b'sep'), [])
with self.assertRaises(TypeError):
bytes_join(memoryview(b'sep'), [])
with self.assertRaises(TypeError):
bytes_join('', []) # empty Unicode string
with self.assertRaises(TypeError):
bytes_join('unicode', [])
with self.assertRaises(TypeError):
bytes_join(123, [])
with self.assertRaises(SystemError):
self.assertEqual(bytes_join(NULL, [b'a', b'b', b'c']), b'abc')

# invalid 'iterable' argument
with self.assertRaises(TypeError):
bytes_join(b'', [b'bytes', 'unicode'])
with self.assertRaises(TypeError):
bytes_join(b'', [b'bytes', 123])
with self.assertRaises(TypeError):
bytes_join(b'', 123)
with self.assertRaises(SystemError):
bytes_join(b'', NULL)


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add :c:func:`PyBytes_Join(sep, iterable) <PyBytes_Join>` function, similar to
``sep.join(iterable)`` in Python. Patch by Victor Stinner.
4 changes: 2 additions & 2 deletions Modules/_io/bufferedio.c
Original file line number Diff line number Diff line change
Expand Up @@ -1283,7 +1283,7 @@ _buffered_readline(buffered *self, Py_ssize_t limit)
Py_CLEAR(res);
goto end;
}
Py_XSETREF(res, _PyBytes_Join((PyObject *)&_Py_SINGLETON(bytes_empty), chunks));
Py_XSETREF(res, PyBytes_Join((PyObject *)&_Py_SINGLETON(bytes_empty), chunks));

end:
LEAVE_BUFFERED(self)
Expand Down Expand Up @@ -1736,7 +1736,7 @@ _bufferedreader_read_all(buffered *self)
goto cleanup;
}
else {
tmp = _PyBytes_Join((PyObject *)&_Py_SINGLETON(bytes_empty), chunks);
tmp = PyBytes_Join((PyObject *)&_Py_SINGLETON(bytes_empty), chunks);
res = tmp;
goto cleanup;
}
Expand Down
2 changes: 1 addition & 1 deletion Modules/_io/iobase.c
Original file line number Diff line number Diff line change
Expand Up @@ -999,7 +999,7 @@ _io__RawIOBase_readall_impl(PyObject *self)
return NULL;
}
}
result = _PyBytes_Join((PyObject *)&_Py_SINGLETON(bytes_empty), chunks);
result = PyBytes_Join((PyObject *)&_Py_SINGLETON(bytes_empty), chunks);
Py_DECREF(chunks);
return result;
}
Expand Down
4 changes: 2 additions & 2 deletions Modules/_sre/sre.c
Original file line number Diff line number Diff line change
Expand Up @@ -1287,7 +1287,7 @@ pattern_subx(_sremodulestate* module_state,
}
else {
if (state.isbytes)
item = _PyBytes_Join(joiner, list);
item = PyBytes_Join(joiner, list);
else
item = PyUnicode_Join(joiner, list);
Py_DECREF(joiner);
Expand Down Expand Up @@ -2918,7 +2918,7 @@ expand_template(TemplateObject *self, MatchObject *match)
}
else {
Py_SET_SIZE(list, count);
result = _PyBytes_Join((PyObject *)&_Py_SINGLETON(bytes_empty), list);
result = PyBytes_Join((PyObject *)&_Py_SINGLETON(bytes_empty), list);
}

cleanup:
Expand Down
15 changes: 15 additions & 0 deletions Modules/_testcapi/bytes.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,23 @@ bytes_resize(PyObject *Py_UNUSED(module), PyObject *args)
}


/* Test PyBytes_Join() */
static PyObject *
bytes_join(PyObject *Py_UNUSED(module), PyObject *args)
{
PyObject *sep, *iterable;
if (!PyArg_ParseTuple(args, "OO", &sep, &iterable)) {
return NULL;
}
NULLABLE(sep);
NULLABLE(iterable);
return PyBytes_Join(sep, iterable);
}


static PyMethodDef test_methods[] = {
{"bytes_resize", bytes_resize, METH_VARARGS},
{"bytes_join", bytes_join, METH_VARARGS},
{NULL},
};

Expand Down
16 changes: 12 additions & 4 deletions Objects/bytesobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1867,11 +1867,19 @@ bytes_join(PyBytesObject *self, PyObject *iterable_of_bytes)
}

PyObject *
_PyBytes_Join(PyObject *sep, PyObject *x)
PyBytes_Join(PyObject *sep, PyObject *iterable)
{
assert(sep != NULL && PyBytes_Check(sep));
assert(x != NULL);
return bytes_join((PyBytesObject*)sep, x);
if (sep == NULL) {
PyErr_BadInternalCall();
return NULL;
}
if (!PyBytes_Check(sep)) {
PyErr_Format(PyExc_TypeError,
"sep: expected bytes, got %T", sep);
return NULL;
}

return stringlib_bytes_join(sep, iterable);
}

/*[clinic input]
Expand Down

0 comments on commit 3d60dfb

Please sign in to comment.