summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/c-api/bytes.rst18
-rw-r--r--Doc/whatsnew/3.14.rst5
-rw-r--r--Include/cpython/bytesobject.h7
-rw-r--r--Lib/test/test_capi/test_bytes.py40
-rw-r--r--Misc/NEWS.d/next/C_API/2024-07-12-13-40-59.gh-issue-121645.96QvD3.rst2
-rw-r--r--Modules/_io/bufferedio.c4
-rw-r--r--Modules/_io/iobase.c2
-rw-r--r--Modules/_sre/sre.c4
-rw-r--r--Modules/_testcapi/bytes.c15
-rw-r--r--Objects/bytesobject.c16
10 files changed, 101 insertions, 12 deletions
diff --git a/Doc/c-api/bytes.rst b/Doc/c-api/bytes.rst
index bca78a9..3d0501c 100644
--- a/Doc/c-api/bytes.rst
+++ b/Doc/c-api/bytes.rst
@@ -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.
diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst
index 44b373a..975af42 100644
--- a/Doc/whatsnew/3.14.rst
+++ b/Doc/whatsnew/3.14.rst
@@ -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
----------------------
diff --git a/Include/cpython/bytesobject.h b/Include/cpython/bytesobject.h
index 4153721..cf3f038 100644
--- a/Include/cpython/bytesobject.h
+++ b/Include/cpython/bytesobject.h
@@ -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
diff --git a/Lib/test/test_capi/test_bytes.py b/Lib/test/test_capi/test_bytes.py
index d5f047b..5908d79 100644
--- a/Lib/test/test_capi/test_bytes.py
+++ b/Lib/test/test_capi/test_bytes.py
@@ -249,6 +249,46 @@ class CAPITest(unittest.TestCase):
# 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()
diff --git a/Misc/NEWS.d/next/C_API/2024-07-12-13-40-59.gh-issue-121645.96QvD3.rst b/Misc/NEWS.d/next/C_API/2024-07-12-13-40-59.gh-issue-121645.96QvD3.rst
new file mode 100644
index 0000000..1cca9b2
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2024-07-12-13-40-59.gh-issue-121645.96QvD3.rst
@@ -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.
diff --git a/Modules/_io/bufferedio.c b/Modules/_io/bufferedio.c
index e45323c..bc5fff5 100644
--- a/Modules/_io/bufferedio.c
+++ b/Modules/_io/bufferedio.c
@@ -1283,7 +1283,7 @@ found:
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)
@@ -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;
}
diff --git a/Modules/_io/iobase.c b/Modules/_io/iobase.c
index 184e0b7..419e551 100644
--- a/Modules/_io/iobase.c
+++ b/Modules/_io/iobase.c
@@ -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;
}
diff --git a/Modules/_sre/sre.c b/Modules/_sre/sre.c
index 01420d1..32f91af 100644
--- a/Modules/_sre/sre.c
+++ b/Modules/_sre/sre.c
@@ -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);
@@ -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:
diff --git a/Modules/_testcapi/bytes.c b/Modules/_testcapi/bytes.c
index 02294d8..33903de 100644
--- a/Modules/_testcapi/bytes.c
+++ b/Modules/_testcapi/bytes.c
@@ -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},
};
diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c
index e88b199..c467b24 100644
--- a/Objects/bytesobject.c
+++ b/Objects/bytesobject.c
@@ -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]