Skip to content

Commit 85c78f7

Browse files
committed
pythongh-121645: Add PyBytes_Join() function
1 parent 65feded commit 85c78f7

File tree

11 files changed

+68
-12
lines changed

11 files changed

+68
-12
lines changed

Doc/c-api/bytes.rst

+10
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,16 @@ 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+
*iterable* must be an iterable object.
198+
199+
.. versionadded: 3.14
200+
201+
192202
.. c:function:: int _PyBytes_Resize(PyObject **bytes, Py_ssize_t newsize)
193203
194204
Resize a bytes object. *newsize* will be the new length of the bytes object.

Doc/whatsnew/3.14.rst

+4
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,10 @@ New Features
357357

358358
(Contributed by Victor Stinner in :gh:`119182`.)
359359

360+
* Add :c:func:`PyBytes_Join(seq, iterable) <PyBytes_Join>` function,
361+
similar to ``sep.join(iterable)`` in Python.
362+
(Contributed by Victor Stinner in :gh:`121645`.)
363+
360364
Porting to Python 3.14
361365
----------------------
362366

Include/cpython/bytesobject.h

+4
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,7 @@ static inline Py_ssize_t PyBytes_GET_SIZE(PyObject *op) {
3131
return Py_SIZE(self);
3232
}
3333
#define PyBytes_GET_SIZE(self) PyBytes_GET_SIZE(_PyObject_CAST(self))
34+
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);

Include/internal/pycore_bytesobject.h

-4
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,6 @@ extern PyObject* _PyBytes_FromHex(
2323
PyAPI_FUNC(PyObject*) _PyBytes_DecodeEscape(const char *, Py_ssize_t,
2424
const char *, const char **);
2525

26-
/* _PyBytes_Join(sep, x) is like sep.join(x). sep must be PyBytesObject*,
27-
x must be an iterable object. */
28-
extern PyObject* _PyBytes_Join(PyObject *sep, PyObject *x);
29-
3026

3127
// Substring Search.
3228
//

Lib/test/test_capi/test_bytes.py

+23
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,29 @@ def test_resize(self):
247247
# CRASHES resize(NULL, 0, False)
248248
# CRASHES resize(NULL, 3, False)
249249

250+
def test_join(self):
251+
"""Test PyBytes_Join()"""
252+
bytes_join = _testcapi.bytes_join
253+
254+
self.assertEqual(bytes_join(b'', []), b'')
255+
self.assertEqual(bytes_join(b'sep', []), b'')
256+
257+
self.assertEqual(bytes_join(b'', [b'a', b'b', b'c']), b'abc')
258+
self.assertEqual(bytes_join(b'-', [b'a', b'b', b'c']), b'a-b-c')
259+
self.assertEqual(bytes_join(b' - ', [b'a', b'b', b'c']), b'a - b - c')
260+
261+
# invalid 'sep' argument type
262+
with self.assertRaises(TypeError):
263+
bytes_join('unicode', [])
264+
with self.assertRaises(TypeError):
265+
bytes_join(123, [])
266+
267+
# invalid 'iterable' argument type
268+
with self.assertRaises(TypeError):
269+
bytes_join(b'', [b'bytes', 'unicode'])
270+
with self.assertRaises(TypeError):
271+
bytes_join(b'', [b'bytes', 123])
272+
250273

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

Modules/_io/bufferedio.c

+2-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
*/
99

1010
#include "Python.h"
11-
#include "pycore_bytesobject.h" // _PyBytes_Join()
1211
#include "pycore_call.h" // _PyObject_CallNoArgs()
1312
#include "pycore_object.h" // _PyObject_GC_UNTRACK()
1413
#include "pycore_pyerrors.h" // _Py_FatalErrorFormat()
@@ -1284,7 +1283,7 @@ _buffered_readline(buffered *self, Py_ssize_t limit)
12841283
Py_CLEAR(res);
12851284
goto end;
12861285
}
1287-
Py_XSETREF(res, _PyBytes_Join((PyObject *)&_Py_SINGLETON(bytes_empty), chunks));
1286+
Py_XSETREF(res, PyBytes_Join((PyObject *)&_Py_SINGLETON(bytes_empty), chunks));
12881287

12891288
end:
12901289
LEAVE_BUFFERED(self)
@@ -1737,7 +1736,7 @@ _bufferedreader_read_all(buffered *self)
17371736
goto cleanup;
17381737
}
17391738
else {
1740-
tmp = _PyBytes_Join((PyObject *)&_Py_SINGLETON(bytes_empty), chunks);
1739+
tmp = PyBytes_Join((PyObject *)&_Py_SINGLETON(bytes_empty), chunks);
17411740
res = tmp;
17421741
goto cleanup;
17431742
}

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

+13
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,21 @@ 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, *seq;
45+
if (!PyArg_ParseTuple(args, "OO", &sep, &seq)) {
46+
return NULL;
47+
}
48+
return PyBytes_Join(sep, seq);
49+
}
50+
51+
4052
static PyMethodDef test_methods[] = {
4153
{"bytes_resize", bytes_resize, METH_VARARGS},
54+
{"bytes_join", bytes_join, METH_VARARGS},
4255
{NULL},
4356
};
4457

Objects/bytesobject.c

+7-2
Original file line numberDiff line numberDiff line change
@@ -1867,10 +1867,15 @@ 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 *x)
18711871
{
1872-
assert(sep != NULL && PyBytes_Check(sep));
1872+
assert(sep != NULL);
18731873
assert(x != NULL);
1874+
if (!PyBytes_Check(sep)) {
1875+
PyErr_Format(PyExc_TypeError,
1876+
"expected bytes for sep argument, got %T", sep);
1877+
return NULL;
1878+
}
18741879
return bytes_join((PyBytesObject*)sep, x);
18751880
}
18761881

0 commit comments

Comments
 (0)