Index: Objects/bytesobject.c =================================================================== --- Objects/bytesobject.c (revision 65608) +++ Objects/bytesobject.c (working copy) @@ -2784,6 +2784,48 @@ return NULL; } +PyDoc_STRVAR(tohex_doc, +"bytes.tohex() -> str\n\ +\n\ +Convert a bytes object into a string of hexadecimal numbers.\n\ +Example: b'\\xb9\\x01\\xef'.tohex() -> 'b901ef'."); + +static Py_UNICODE +int_to_hex_digit(unsigned int b) +{ + assert(b < 16); + if (b < 10) + return b + '0'; + else + return b + 'a' - 10; +} + +static PyObject * +string_tohex(PyObject *self, PyObject *args) +{ + PyObject *hex; + Py_UNICODE *hexbuf; + char *bytes = NULL; + Py_ssize_t byteslen = 0, i, j; + unsigned char byte; + + PyBytes_AsStringAndSize(self, &bytes, &byteslen); + hex = PyUnicode_FromUnicode(NULL, byteslen*2); + if (!hex) + return NULL; + hexbuf = PyUnicode_AS_UNICODE(hex); + assert(hexbuf); + + for (i = j = 0; i < byteslen; i++) { + byte = (unsigned char) bytes[i]; + hexbuf[j] = int_to_hex_digit(byte / 16); + hexbuf[j+1] = int_to_hex_digit(byte % 16); + j += 2; + } + + return hex; +} + PyDoc_STRVAR(sizeof__doc__, "S.__sizeof__() -> size of S in memory, in bytes"); @@ -2855,6 +2897,7 @@ {"swapcase", (PyCFunction)stringlib_swapcase, METH_NOARGS, _Py_swapcase__doc__}, {"title", (PyCFunction)stringlib_title, METH_NOARGS, _Py_title__doc__}, + {"tohex", (PyCFunction)string_tohex, METH_NOARGS, tohex_doc}, {"translate", (PyCFunction)string_translate, METH_VARARGS, translate__doc__}, {"upper", (PyCFunction)stringlib_upper, METH_NOARGS, _Py_upper__doc__}, Index: Objects/bytearrayobject.c =================================================================== --- Objects/bytearrayobject.c (revision 65608) +++ Objects/bytearrayobject.c (working copy) @@ -2998,6 +2998,49 @@ return NULL; } +PyDoc_STRVAR(tohex_doc, +"bytearray.tohex() -> str\n\ +\n\ +Convert a bytearray object into a string of hexadecimal numbers.\n\ +Example: bytearray(b'\\xb9\\x01\\xef').tohex() -> 'b901ef'."); + +static Py_UNICODE +int_to_hex_digit(unsigned int b) +{ + assert(b < 16); + if (b < 10) + return b + '0'; + else + return b + 'a' - 10; +} + +static PyObject * +string_tohex(PyObject *self, PyObject *args) +{ + PyObject *hex; + Py_UNICODE *hexbuf; + char *bytes; + Py_ssize_t byteslen, i, j; + unsigned char byte; + + bytes = PyByteArray_AS_STRING(self); + byteslen = PyByteArray_GET_SIZE(self); + hex = PyUnicode_FromUnicode(NULL, byteslen*2); + if (!hex) + return NULL; + hexbuf = PyUnicode_AS_UNICODE(hex); + assert(hexbuf); + + for (i = j = 0; i < byteslen; i++) { + byte = (unsigned char) bytes[i]; + hexbuf[j] = int_to_hex_digit(byte / 16); + hexbuf[j+1] = int_to_hex_digit(byte % 16); + j += 2; + } + + return hex; +} + PyDoc_STRVAR(reduce_doc, "Return state information for pickling."); static PyObject * @@ -3115,6 +3158,7 @@ {"swapcase", (PyCFunction)stringlib_swapcase, METH_NOARGS, _Py_swapcase__doc__}, {"title", (PyCFunction)stringlib_title, METH_NOARGS, _Py_title__doc__}, + {"tohex", (PyCFunction)string_tohex, METH_NOARGS, tohex_doc}, {"translate", (PyCFunction)bytes_translate, METH_VARARGS, translate__doc__}, {"upper", (PyCFunction)stringlib_upper, METH_NOARGS, _Py_upper__doc__}, Index: Doc/library/stdtypes.rst =================================================================== --- Doc/library/stdtypes.rst (revision 65608) +++ Doc/library/stdtypes.rst (working copy) @@ -1477,7 +1477,7 @@ b = a.replace(b"a", b"f") -The bytes and bytearray types have an additional class method: +The bytes and bytearray types have some additional methods: .. method:: bytes.fromhex(string) bytearray.fromhex(string) @@ -1489,6 +1489,15 @@ >>> bytes.fromhex('f0 f1f2 ') b'\xf0\xf1\xf2' +.. method:: bytes.tohex() + bytearray.tohex() + + Returns a string containing the bytes encoded in hexadecimal. + Each byte is encoded into two hexadecimal digits. + + >>> b'\xf0\xf1\xf2'.tohex() + 'f0f1f2' + .. XXX verify/document translate() semantics! .. method:: bytes.translate(table[, delete]) Index: Lib/test/test_bytes.py =================================================================== --- Lib/test/test_bytes.py (revision 65608) +++ Lib/test/test_bytes.py (working copy) @@ -256,6 +256,14 @@ self.assertRaises(ValueError, self.type2test.fromhex, '\x00') self.assertRaises(ValueError, self.type2test.fromhex, '12 \x00 34') + def test_tohex(self): + self.assertEquals(self.type2test().tohex(), '') + given = self.type2test([0x1a, 0x2b, 0x30]) + self.assertEquals(given.tohex(), '1a2b30') + given = self.type2test(range(256)) + expect = ''.join('%02x' % b for b in range(256)) + self.assertEquals(given.tohex(), expect) + def test_join(self): self.assertEqual(self.type2test(b"").join([]), b"") self.assertEqual(self.type2test(b"").join([b""]), b"")