Skip to content

Commit c15a073

Browse files
committed
make bytes(o) respect __bytes__ python#2415
This adds two new C-API functions: PyObject_Bytes and PyBytes_FromObject. Reviewer: Barry
1 parent a786b02 commit c15a073

File tree

8 files changed

+83
-2
lines changed

8 files changed

+83
-2
lines changed

Doc/c-api/bytes.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,12 @@ called with a non-bytes parameter.
118118
arguments.
119119

120120

121+
.. cfunction:: PyObject* PyBytes_FromObject(PyObject *o)
122+
123+
Return the bytes representation of object *o* that implements the buffer
124+
protocol.
125+
126+
121127
.. cfunction:: Py_ssize_t PyBytes_Size(PyObject *o)
122128

123129
Return the length of the bytes in bytes object *o*.

Doc/c-api/object.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,14 @@ Object Protocol
139139
Python expression ``str(o)``. Called by the :func:`str` built-in function
140140
and, therefore, by the :func:`print` function.
141141

142+
.. cfunction:: PyObject* PyObject_Bytes(PyObject *o)
143+
144+
.. index:: builtin: bytes
145+
146+
Compute a bytes representation of object *o*. *NULL* is returned on failure
147+
and a bytes object on success. This is equivalent to the Python expression
148+
``bytes(o)``.
149+
142150

143151
.. cfunction:: int PyObject_IsInstance(PyObject *inst, PyObject *cls)
144152

Include/bytesobject.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ PyAPI_DATA(PyTypeObject) PyBytesIter_Type;
4848

4949
PyAPI_FUNC(PyObject *) PyBytes_FromStringAndSize(const char *, Py_ssize_t);
5050
PyAPI_FUNC(PyObject *) PyBytes_FromString(const char *);
51+
PyAPI_FUNC(PyObject *) PyBytes_FromObject(PyObject *);
5152
PyAPI_FUNC(PyObject *) PyBytes_FromFormatV(const char*, va_list)
5253
Py_GCC_ATTRIBUTE((format(printf, 1, 0)));
5354
PyAPI_FUNC(PyObject *) PyBytes_FromFormat(const char*, ...)

Include/object.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,7 @@ PyAPI_FUNC(void) _PyObject_Dump(PyObject *);
423423
PyAPI_FUNC(PyObject *) PyObject_Repr(PyObject *);
424424
PyAPI_FUNC(PyObject *) PyObject_Str(PyObject *);
425425
PyAPI_FUNC(PyObject *) PyObject_ASCII(PyObject *);
426+
PyAPI_FUNC(PyObject *) PyObject_Bytes(PyObject *);
426427
PyAPI_FUNC(int) PyObject_Compare(PyObject *, PyObject *);
427428
PyAPI_FUNC(PyObject *) PyObject_RichCompare(PyObject *, PyObject *, int);
428429
PyAPI_FUNC(int) PyObject_RichCompareBool(PyObject *, PyObject *, int);

Lib/test/test_bytes.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,18 @@ def test_buffer_is_readonly(self):
458458
with open(fd, "rb", buffering=0) as f:
459459
self.assertRaises(TypeError, f.readinto, b"")
460460

461+
def test_custom(self):
462+
class A:
463+
def __bytes__(self):
464+
return b'abc'
465+
self.assertEqual(bytes(A()), b'abc')
466+
class A: pass
467+
self.assertRaises(TypeError, bytes, A())
468+
class A:
469+
def __bytes__(self):
470+
return None
471+
self.assertRaises(TypeError, bytes, A())
472+
461473

462474
class ByteArrayTest(BaseBytesTest):
463475
type2test = bytearray

Misc/NEWS

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ Core and Builtins
2727

2828
- Issue #3650: Fixed a reference leak in bytes.split('x').
2929

30+
- bytes(o) now tries to use o.__bytes__() before using fallbacks.
31+
32+
C API
33+
-----
34+
35+
- PyObject_Bytes and PyBytes_FromObject were added.
36+
3037
Library
3138
-------
3239

Objects/bytesobject.c

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2882,11 +2882,10 @@ str_subtype_new(PyTypeObject *type, PyObject *args, PyObject *kwds);
28822882
static PyObject *
28832883
string_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
28842884
{
2885-
PyObject *x = NULL, *it;
2885+
PyObject *x = NULL;
28862886
const char *encoding = NULL;
28872887
const char *errors = NULL;
28882888
PyObject *new = NULL;
2889-
Py_ssize_t i, size;
28902889
static char *kwlist[] = {"source", "encoding", "errors", 0};
28912890

28922891
if (type != &PyBytes_Type)
@@ -2924,6 +2923,14 @@ string_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
29242923
"encoding or errors without a string argument");
29252924
return NULL;
29262925
}
2926+
return PyObject_Bytes(x);
2927+
}
2928+
2929+
PyObject *
2930+
PyBytes_FromObject(PyObject *x)
2931+
{
2932+
PyObject *new, *it;
2933+
Py_ssize_t i, size;
29272934

29282935
/* Is it an int? */
29292936
size = PyNumber_AsSsize_t(x, PyExc_ValueError);

Objects/object.c

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,45 @@ PyObject_ASCII(PyObject *v)
453453
return res;
454454
}
455455

456+
PyObject *
457+
PyObject_Bytes(PyObject *v)
458+
{
459+
PyObject *bytesmeth, *result, *func;
460+
static PyObject *bytesstring = NULL;
461+
462+
if (bytesstring == NULL) {
463+
bytesstring = PyUnicode_InternFromString("__bytes__");
464+
if (bytesstring == NULL)
465+
return NULL;
466+
}
467+
468+
if (v == NULL)
469+
return PyBytes_FromString("<NULL>");
470+
471+
if (PyBytes_CheckExact(v)) {
472+
Py_INCREF(v);
473+
return v;
474+
}
475+
476+
/* Doesn't create a reference */
477+
func = _PyType_Lookup(Py_TYPE(v), bytesstring);
478+
if (func != NULL) {
479+
result = PyObject_CallFunctionObjArgs(func, v, NULL);
480+
if (result == NULL)
481+
return NULL;
482+
if (!PyBytes_Check(result)) {
483+
PyErr_Format(PyExc_TypeError,
484+
"__bytes__ returned non-bytes (type %.200s)",
485+
Py_TYPE(result)->tp_name);
486+
Py_DECREF(result);
487+
return NULL;
488+
}
489+
return result;
490+
}
491+
PyErr_Clear();
492+
return PyBytes_FromObject(v);
493+
}
494+
456495
/* The new comparison philosophy is: we completely separate three-way
457496
comparison from rich comparison. That is, PyObject_Compare() and
458497
PyObject_Cmp() *just* use the tp_compare slot. And PyObject_RichCompare()

0 commit comments

Comments
 (0)