Skip to content

Commit

Permalink
pythongh-121645: Add PyBytes_Join() function
Browse files Browse the repository at this point in the history
  • Loading branch information
vstinner committed Jul 12, 2024
1 parent 65feded commit 45a36a0
Show file tree
Hide file tree
Showing 11 changed files with 68 additions and 12 deletions.
10 changes: 10 additions & 0 deletions Doc/c-api/bytes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,16 @@ 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)``.
*sep* must be Python :class:`bytes` object.
*iterable* must be an iterable object.
.. 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
4 changes: 4 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,10 @@ New Features

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

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

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

Expand Down
4 changes: 4 additions & 0 deletions Include/cpython/bytesobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,7 @@ static inline Py_ssize_t PyBytes_GET_SIZE(PyObject *op) {
return Py_SIZE(self);
}
#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);
4 changes: 0 additions & 4 deletions Include/internal/pycore_bytesobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@ extern PyObject* _PyBytes_FromHex(
PyAPI_FUNC(PyObject*) _PyBytes_DecodeEscape(const char *, Py_ssize_t,
const char *, const char **);

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


// Substring Search.
//
Expand Down
23 changes: 23 additions & 0 deletions Lib/test/test_capi/test_bytes.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,29 @@ 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')

# invalid 'sep' argument type
with self.assertRaises(TypeError):
bytes_join('unicode', [])
with self.assertRaises(TypeError):
bytes_join(123, [])

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


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add :c:func:`PyBytes_Join(seq, iterable) <PyBytes_Join>` function similar to
``sep.join(iterable)``. Patch by Victor Stinner.
5 changes: 2 additions & 3 deletions Modules/_io/bufferedio.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
*/

#include "Python.h"
#include "pycore_bytesobject.h" // _PyBytes_Join()
#include "pycore_call.h" // _PyObject_CallNoArgs()
#include "pycore_object.h" // _PyObject_GC_UNTRACK()
#include "pycore_pyerrors.h" // _Py_FatalErrorFormat()
Expand Down Expand Up @@ -1284,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 @@ -1737,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
13 changes: 13 additions & 0 deletions Modules/_testcapi/bytes.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,21 @@ bytes_resize(PyObject *Py_UNUSED(module), PyObject *args)
}


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


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

Expand Down
9 changes: 7 additions & 2 deletions Objects/bytesobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1867,10 +1867,15 @@ bytes_join(PyBytesObject *self, PyObject *iterable_of_bytes)
}

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

Expand Down

0 comments on commit 45a36a0

Please sign in to comment.