Skip to content

Commit 3d60dfb

Browse files
authored
gh-121645: Add PyBytes_Join() function (#121646)
* Replace _PyBytes_Join() with PyBytes_Join(). * Keep _PyBytes_Join() as an alias to PyBytes_Join().
1 parent 7fca268 commit 3d60dfb

File tree

10 files changed

+101
-12
lines changed

10 files changed

+101
-12
lines changed

Doc/c-api/bytes.rst

+18
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,24 @@ called with a non-bytes parameter.
189189
to *newpart* (i.e. decrements its reference count).
190190
191191
192+
.. c:function:: PyObject* PyBytes_Join(PyObject *sep, PyObject *iterable)
193+
194+
Similar to ``sep.join(iterable)`` in Python.
195+
196+
*sep* must be Python :class:`bytes` object.
197+
(Note that :c:func:`PyUnicode_Join` accepts ``NULL`` separator and treats
198+
it as a space, whereas :c:func:`PyBytes_Join` doesn't accept ``NULL``
199+
separator.)
200+
201+
*iterable* must be an iterable object yielding objects that implement the
202+
:ref:`buffer protocol <bufferobjects>`.
203+
204+
On success, return a new :class:`bytes` object.
205+
On error, set an exception and return ``NULL``.
206+
207+
.. versionadded: 3.14
208+
209+
192210
.. c:function:: int _PyBytes_Resize(PyObject **bytes, Py_ssize_t newsize)
193211
194212
Resize a bytes object. *newsize* will be the new length of the bytes object.

Doc/whatsnew/3.14.rst

+5
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,11 @@ New Features
485485

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

488+
* Add :c:func:`PyBytes_Join(sep, iterable) <PyBytes_Join>` function,
489+
similar to ``sep.join(iterable)`` in Python.
490+
(Contributed by Victor Stinner in :gh:`121645`.)
491+
492+
488493
Porting to Python 3.14
489494
----------------------
490495

Include/cpython/bytesobject.h

+4-3
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ static inline Py_ssize_t PyBytes_GET_SIZE(PyObject *op) {
3232
}
3333
#define PyBytes_GET_SIZE(self) PyBytes_GET_SIZE(_PyObject_CAST(self))
3434

35-
/* _PyBytes_Join(sep, x) is like sep.join(x). sep must be PyBytesObject*,
36-
x must be an iterable object. */
37-
PyAPI_FUNC(PyObject*) _PyBytes_Join(PyObject *sep, PyObject *x);
35+
PyAPI_FUNC(PyObject*) PyBytes_Join(PyObject *sep, PyObject *iterable);
36+
37+
// Alias kept for backward compatibility
38+
#define _PyBytes_Join PyBytes_Join

Lib/test/test_capi/test_bytes.py

+40
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,46 @@ def test_resize(self):
249249
# CRASHES resize(NULL, 0, False)
250250
# CRASHES resize(NULL, 3, False)
251251

252+
def test_join(self):
253+
"""Test PyBytes_Join()"""
254+
bytes_join = _testcapi.bytes_join
255+
256+
self.assertEqual(bytes_join(b'', []), b'')
257+
self.assertEqual(bytes_join(b'sep', []), b'')
258+
259+
self.assertEqual(bytes_join(b'', [b'a', b'b', b'c']), b'abc')
260+
self.assertEqual(bytes_join(b'-', [b'a', b'b', b'c']), b'a-b-c')
261+
self.assertEqual(bytes_join(b' - ', [b'a', b'b', b'c']), b'a - b - c')
262+
self.assertEqual(bytes_join(b'-', [bytearray(b'abc'),
263+
memoryview(b'def')]),
264+
b'abc-def')
265+
266+
self.assertEqual(bytes_join(b'-', iter([b'a', b'b', b'c'])), b'a-b-c')
267+
268+
# invalid 'sep' argument
269+
with self.assertRaises(TypeError):
270+
bytes_join(bytearray(b'sep'), [])
271+
with self.assertRaises(TypeError):
272+
bytes_join(memoryview(b'sep'), [])
273+
with self.assertRaises(TypeError):
274+
bytes_join('', []) # empty Unicode string
275+
with self.assertRaises(TypeError):
276+
bytes_join('unicode', [])
277+
with self.assertRaises(TypeError):
278+
bytes_join(123, [])
279+
with self.assertRaises(SystemError):
280+
self.assertEqual(bytes_join(NULL, [b'a', b'b', b'c']), b'abc')
281+
282+
# invalid 'iterable' argument
283+
with self.assertRaises(TypeError):
284+
bytes_join(b'', [b'bytes', 'unicode'])
285+
with self.assertRaises(TypeError):
286+
bytes_join(b'', [b'bytes', 123])
287+
with self.assertRaises(TypeError):
288+
bytes_join(b'', 123)
289+
with self.assertRaises(SystemError):
290+
bytes_join(b'', NULL)
291+
252292

253293
if __name__ == "__main__":
254294
unittest.main()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add :c:func:`PyBytes_Join(sep, iterable) <PyBytes_Join>` function, similar to
2+
``sep.join(iterable)`` in Python. Patch by Victor Stinner.

Modules/_io/bufferedio.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -1283,7 +1283,7 @@ _buffered_readline(buffered *self, Py_ssize_t limit)
12831283
Py_CLEAR(res);
12841284
goto end;
12851285
}
1286-
Py_XSETREF(res, _PyBytes_Join((PyObject *)&_Py_SINGLETON(bytes_empty), chunks));
1286+
Py_XSETREF(res, PyBytes_Join((PyObject *)&_Py_SINGLETON(bytes_empty), chunks));
12871287

12881288
end:
12891289
LEAVE_BUFFERED(self)
@@ -1736,7 +1736,7 @@ _bufferedreader_read_all(buffered *self)
17361736
goto cleanup;
17371737
}
17381738
else {
1739-
tmp = _PyBytes_Join((PyObject *)&_Py_SINGLETON(bytes_empty), chunks);
1739+
tmp = PyBytes_Join((PyObject *)&_Py_SINGLETON(bytes_empty), chunks);
17401740
res = tmp;
17411741
goto cleanup;
17421742
}

Modules/_io/iobase.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -999,7 +999,7 @@ _io__RawIOBase_readall_impl(PyObject *self)
999999
return NULL;
10001000
}
10011001
}
1002-
result = _PyBytes_Join((PyObject *)&_Py_SINGLETON(bytes_empty), chunks);
1002+
result = PyBytes_Join((PyObject *)&_Py_SINGLETON(bytes_empty), chunks);
10031003
Py_DECREF(chunks);
10041004
return result;
10051005
}

Modules/_sre/sre.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -1287,7 +1287,7 @@ pattern_subx(_sremodulestate* module_state,
12871287
}
12881288
else {
12891289
if (state.isbytes)
1290-
item = _PyBytes_Join(joiner, list);
1290+
item = PyBytes_Join(joiner, list);
12911291
else
12921292
item = PyUnicode_Join(joiner, list);
12931293
Py_DECREF(joiner);
@@ -2918,7 +2918,7 @@ expand_template(TemplateObject *self, MatchObject *match)
29182918
}
29192919
else {
29202920
Py_SET_SIZE(list, count);
2921-
result = _PyBytes_Join((PyObject *)&_Py_SINGLETON(bytes_empty), list);
2921+
result = PyBytes_Join((PyObject *)&_Py_SINGLETON(bytes_empty), list);
29222922
}
29232923

29242924
cleanup:

Modules/_testcapi/bytes.c

+15
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,23 @@ bytes_resize(PyObject *Py_UNUSED(module), PyObject *args)
3737
}
3838

3939

40+
/* Test PyBytes_Join() */
41+
static PyObject *
42+
bytes_join(PyObject *Py_UNUSED(module), PyObject *args)
43+
{
44+
PyObject *sep, *iterable;
45+
if (!PyArg_ParseTuple(args, "OO", &sep, &iterable)) {
46+
return NULL;
47+
}
48+
NULLABLE(sep);
49+
NULLABLE(iterable);
50+
return PyBytes_Join(sep, iterable);
51+
}
52+
53+
4054
static PyMethodDef test_methods[] = {
4155
{"bytes_resize", bytes_resize, METH_VARARGS},
56+
{"bytes_join", bytes_join, METH_VARARGS},
4257
{NULL},
4358
};
4459

Objects/bytesobject.c

+12-4
Original file line numberDiff line numberDiff line change
@@ -1867,11 +1867,19 @@ bytes_join(PyBytesObject *self, PyObject *iterable_of_bytes)
18671867
}
18681868

18691869
PyObject *
1870-
_PyBytes_Join(PyObject *sep, PyObject *x)
1870+
PyBytes_Join(PyObject *sep, PyObject *iterable)
18711871
{
1872-
assert(sep != NULL && PyBytes_Check(sep));
1873-
assert(x != NULL);
1874-
return bytes_join((PyBytesObject*)sep, x);
1872+
if (sep == NULL) {
1873+
PyErr_BadInternalCall();
1874+
return NULL;
1875+
}
1876+
if (!PyBytes_Check(sep)) {
1877+
PyErr_Format(PyExc_TypeError,
1878+
"sep: expected bytes, got %T", sep);
1879+
return NULL;
1880+
}
1881+
1882+
return stringlib_bytes_join(sep, iterable);
18751883
}
18761884

18771885
/*[clinic input]

0 commit comments

Comments
 (0)