Index: Include/object.h =================================================================== --- Include/object.h (revision 65931) +++ Include/object.h (working copy) @@ -423,6 +423,7 @@ 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); Index: Include/bytesobject.h =================================================================== --- Include/bytesobject.h (revision 65931) +++ Include/bytesobject.h (working copy) @@ -48,6 +48,7 @@ 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*, ...) Index: Objects/object.c =================================================================== --- Objects/object.c (revision 65931) +++ Objects/object.c (working copy) @@ -453,6 +453,45 @@ 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() Index: Objects/bytesobject.c =================================================================== --- Objects/bytesobject.c (revision 65931) +++ Objects/bytesobject.c (working copy) @@ -2870,11 +2870,10 @@ 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) @@ -2912,7 +2911,15 @@ "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); if (size == -1 && PyErr_Occurred()) { Index: Lib/test/test_bytes.py =================================================================== --- Lib/test/test_bytes.py (revision 65931) +++ Lib/test/test_bytes.py (working copy) @@ -458,7 +458,19 @@ 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