diff --git a/Doc/c-api/bytes.rst b/Doc/c-api/bytes.rst index 916e20eaf8e..c9a114a9382 100644 --- a/Doc/c-api/bytes.rst +++ b/Doc/c-api/bytes.rst @@ -118,6 +118,12 @@ called with a non-bytes parameter. arguments. +.. cfunction:: PyObject* PyBytes_FromObject(PyObject *o) + + Return the bytes representation of object *o* that implements the buffer + protocol. + + .. cfunction:: Py_ssize_t PyBytes_Size(PyObject *o) Return the length of the bytes in bytes object *o*. diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst index 193ab958f47..7b9682cb2b0 100644 --- a/Doc/c-api/object.rst +++ b/Doc/c-api/object.rst @@ -139,6 +139,14 @@ Object Protocol Python expression ``str(o)``. Called by the :func:`str` built-in function and, therefore, by the :func:`print` function. +.. cfunction:: PyObject* PyObject_Bytes(PyObject *o) + + .. index:: builtin: bytes + + Compute a bytes representation of object *o*. *NULL* is returned on failure + and a bytes object on success. This is equivalent to the Python expression + ``bytes(o)``. + .. cfunction:: int PyObject_IsInstance(PyObject *inst, PyObject *cls) diff --git a/Include/bytesobject.h b/Include/bytesobject.h index ece06d26675..3f275a86850 100644 --- a/Include/bytesobject.h +++ b/Include/bytesobject.h @@ -48,6 +48,7 @@ PyAPI_DATA(PyTypeObject) PyBytesIter_Type; PyAPI_FUNC(PyObject *) PyBytes_FromStringAndSize(const char *, Py_ssize_t); PyAPI_FUNC(PyObject *) PyBytes_FromString(const char *); +PyAPI_FUNC(PyObject *) PyBytes_FromObject(PyObject *); PyAPI_FUNC(PyObject *) PyBytes_FromFormatV(const char*, va_list) Py_GCC_ATTRIBUTE((format(printf, 1, 0))); PyAPI_FUNC(PyObject *) PyBytes_FromFormat(const char*, ...) diff --git a/Include/object.h b/Include/object.h index 372eada548d..60584a02c33 100644 --- a/Include/object.h +++ b/Include/object.h @@ -423,6 +423,7 @@ PyAPI_FUNC(void) _PyObject_Dump(PyObject *); PyAPI_FUNC(PyObject *) PyObject_Repr(PyObject *); PyAPI_FUNC(PyObject *) PyObject_Str(PyObject *); PyAPI_FUNC(PyObject *) PyObject_ASCII(PyObject *); +PyAPI_FUNC(PyObject *) PyObject_Bytes(PyObject *); PyAPI_FUNC(int) PyObject_Compare(PyObject *, PyObject *); PyAPI_FUNC(PyObject *) PyObject_RichCompare(PyObject *, PyObject *, int); PyAPI_FUNC(int) PyObject_RichCompareBool(PyObject *, PyObject *, int); diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index 630f862a151..81d2dad3d21 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -458,6 +458,18 @@ def test_buffer_is_readonly(self): with open(fd, "rb", buffering=0) as f: self.assertRaises(TypeError, f.readinto, b"") + def test_custom(self): + class A: + def __bytes__(self): + return b'abc' + self.assertEqual(bytes(A()), b'abc') + class A: pass + self.assertRaises(TypeError, bytes, A()) + class A: + def __bytes__(self): + return None + self.assertRaises(TypeError, bytes, A()) + class ByteArrayTest(BaseBytesTest): type2test = bytearray diff --git a/Misc/NEWS b/Misc/NEWS index 8594c99e6f5..d7791324223 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -27,6 +27,13 @@ Core and Builtins - Issue #3650: Fixed a reference leak in bytes.split('x'). +- bytes(o) now tries to use o.__bytes__() before using fallbacks. + +C API +----- + +- PyObject_Bytes and PyBytes_FromObject were added. + Library ------- diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c index d59e79a22a3..3bda6d99459 100644 --- a/Objects/bytesobject.c +++ b/Objects/bytesobject.c @@ -2882,11 +2882,10 @@ str_subtype_new(PyTypeObject *type, PyObject *args, PyObject *kwds); static PyObject * string_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { - PyObject *x = NULL, *it; + PyObject *x = NULL; const char *encoding = NULL; const char *errors = NULL; PyObject *new = NULL; - Py_ssize_t i, size; static char *kwlist[] = {"source", "encoding", "errors", 0}; if (type != &PyBytes_Type) @@ -2924,6 +2923,14 @@ string_new(PyTypeObject *type, PyObject *args, PyObject *kwds) "encoding or errors without a string argument"); return NULL; } + return PyObject_Bytes(x); +} + +PyObject * +PyBytes_FromObject(PyObject *x) +{ + PyObject *new, *it; + Py_ssize_t i, size; /* Is it an int? */ size = PyNumber_AsSsize_t(x, PyExc_ValueError); diff --git a/Objects/object.c b/Objects/object.c index 79f8288a4f5..206bb88d5f8 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -453,6 +453,45 @@ PyObject_ASCII(PyObject *v) return res; } +PyObject * +PyObject_Bytes(PyObject *v) +{ + PyObject *bytesmeth, *result, *func; + static PyObject *bytesstring = NULL; + + if (bytesstring == NULL) { + bytesstring = PyUnicode_InternFromString("__bytes__"); + if (bytesstring == NULL) + return NULL; + } + + if (v == NULL) + return PyBytes_FromString(""); + + if (PyBytes_CheckExact(v)) { + Py_INCREF(v); + return v; + } + + /* Doesn't create a reference */ + func = _PyType_Lookup(Py_TYPE(v), bytesstring); + if (func != NULL) { + result = PyObject_CallFunctionObjArgs(func, v, NULL); + if (result == NULL) + return NULL; + if (!PyBytes_Check(result)) { + PyErr_Format(PyExc_TypeError, + "__bytes__ returned non-bytes (type %.200s)", + Py_TYPE(result)->tp_name); + Py_DECREF(result); + return NULL; + } + return result; + } + PyErr_Clear(); + return PyBytes_FromObject(v); +} + /* The new comparison philosophy is: we completely separate three-way comparison from rich comparison. That is, PyObject_Compare() and PyObject_Cmp() *just* use the tp_compare slot. And PyObject_RichCompare()