From 1b6c6da85d7fd268825e09cc8ff339540c1f77bc Mon Sep 17 00:00:00 2001 From: Martin Panter Date: Sat, 27 Aug 2016 08:35:02 +0000 Subject: [PATCH] Issue #27506: Support bytes/bytearray.translate() delete as keyword argument Patch by Xiang Zhang. --- Doc/library/stdtypes.rst | 7 +++-- Lib/test/test_bytes.py | 49 +++++++++++++++++++----------- Misc/NEWS | 3 ++ Objects/bytearrayobject.c | 13 +++----- Objects/bytesobject.c | 10 +++--- Objects/clinic/bytearrayobject.c.h | 37 +++++++++------------- Objects/clinic/bytesobject.c.h | 37 +++++++++------------- 7 files changed, 76 insertions(+), 80 deletions(-) diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index 76ecd01c336..0c7249d96ef 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -2631,8 +2631,8 @@ arbitrary binary data. The prefix(es) to search for may be any :term:`bytes-like object`. -.. method:: bytes.translate(table[, delete]) - bytearray.translate(table[, delete]) +.. method:: bytes.translate(table, delete=b'') + bytearray.translate(table, delete=b'') Return a copy of the bytes or bytearray object where all bytes occurring in the optional argument *delete* are removed, and the remaining bytes have @@ -2648,6 +2648,9 @@ arbitrary binary data. >>> b'read this short text'.translate(None, b'aeiou') b'rd ths shrt txt' + .. versionchanged:: 3.6 + *delete* is now supported as a keyword argument. + The following methods on bytes and bytearray objects have default behaviours that assume the use of ASCII compatible binary formats, but can still be used diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index 64644e7ffba..8bbd669fc2a 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -689,6 +689,37 @@ class BaseBytesTest: test.support.check_free_after_iterating(self, iter, self.type2test) test.support.check_free_after_iterating(self, reversed, self.type2test) + def test_translate(self): + b = self.type2test(b'hello') + rosetta = bytearray(range(256)) + rosetta[ord('o')] = ord('e') + + self.assertRaises(TypeError, b.translate) + self.assertRaises(TypeError, b.translate, None, None) + self.assertRaises(ValueError, b.translate, bytes(range(255))) + + c = b.translate(rosetta, b'hello') + self.assertEqual(b, b'hello') + self.assertIsInstance(c, self.type2test) + + c = b.translate(rosetta) + d = b.translate(rosetta, b'') + self.assertEqual(c, d) + self.assertEqual(c, b'helle') + + c = b.translate(rosetta, b'l') + self.assertEqual(c, b'hee') + c = b.translate(None, b'e') + self.assertEqual(c, b'hllo') + + # test delete as a keyword argument + c = b.translate(rosetta, delete=b'') + self.assertEqual(c, b'helle') + c = b.translate(rosetta, delete=b'l') + self.assertEqual(c, b'hee') + c = b.translate(None, delete=b'e') + self.assertEqual(c, b'hllo') + class BytesTest(BaseBytesTest, unittest.TestCase): type2test = bytes @@ -1449,24 +1480,6 @@ class AssortedBytesTest(unittest.TestCase): self.assertRaises(SyntaxError, eval, 'b"%s"' % chr(c)) - def test_translate(self): - b = b'hello' - ba = bytearray(b) - rosetta = bytearray(range(0, 256)) - rosetta[ord('o')] = ord('e') - c = b.translate(rosetta, b'l') - self.assertEqual(b, b'hello') - self.assertEqual(c, b'hee') - c = ba.translate(rosetta, b'l') - self.assertEqual(ba, b'hello') - self.assertEqual(c, b'hee') - c = b.translate(None, b'e') - self.assertEqual(c, b'hllo') - c = ba.translate(None, b'e') - self.assertEqual(c, b'hllo') - self.assertRaises(TypeError, b.translate, None, None) - self.assertRaises(TypeError, ba.translate, None, None) - def test_split_bytearray(self): self.assertEqual(b'a b'.split(memoryview(b' ')), [b'a', b'b']) diff --git a/Misc/NEWS b/Misc/NEWS index 37c44209b18..8ae30e2cf1e 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -10,6 +10,9 @@ What's New in Python 3.6.0 beta 1 Core and Builtins ----------------- +- Issue #27506: Support passing the bytes/bytearray.translate() "delete" + argument by keyword. + - Issue #27587: Fix another issue found by PVS-Studio: Null pointer check after use of 'def' in _PyState_AddModule(). Initial patch by Christian Heimes. diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index de2dca95ec8..b6631f9ac5c 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -1175,21 +1175,19 @@ bytearray.translate table: object Translation table, which must be a bytes object of length 256. - [ - deletechars: object - ] / + delete as deletechars: object(c_default="NULL") = b'' Return a copy with each character mapped by the given translation table. -All characters occurring in the optional argument deletechars are removed. +All characters occurring in the optional argument delete are removed. The remaining characters are mapped through the given translation table. [clinic start generated code]*/ static PyObject * bytearray_translate_impl(PyByteArrayObject *self, PyObject *table, - int group_right_1, PyObject *deletechars) -/*[clinic end generated code: output=2bebc86a9a1ff083 input=846a01671bccc1c5]*/ + PyObject *deletechars) +/*[clinic end generated code: output=b6a8f01c2a74e446 input=cfff956d4d127a9b]*/ { char *input, *output; const char *table_chars; @@ -1258,8 +1256,7 @@ bytearray_translate_impl(PyByteArrayObject *self, PyObject *table, for (i = inlen; --i >= 0; ) { c = Py_CHARMASK(*input++); if (trans_table[c] != -1) - if (Py_CHARMASK(*output++ = (char)trans_table[c]) == c) - continue; + *output++ = (char)trans_table[c]; } /* Fix the size of the resulting string */ if (inlen > 0) diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c index ff87dfe775c..4fdaa526a6c 100644 --- a/Objects/bytesobject.c +++ b/Objects/bytesobject.c @@ -2045,21 +2045,19 @@ bytes.translate table: object Translation table, which must be a bytes object of length 256. - [ - deletechars: object - ] / + delete as deletechars: object(c_default="NULL") = b'' Return a copy with each character mapped by the given translation table. -All characters occurring in the optional argument deletechars are removed. +All characters occurring in the optional argument delete are removed. The remaining characters are mapped through the given translation table. [clinic start generated code]*/ static PyObject * -bytes_translate_impl(PyBytesObject *self, PyObject *table, int group_right_1, +bytes_translate_impl(PyBytesObject *self, PyObject *table, PyObject *deletechars) -/*[clinic end generated code: output=233df850eb50bf8d input=ca20edf39d780d49]*/ +/*[clinic end generated code: output=43be3437f1956211 input=0ecdf159f654233c]*/ { char *input, *output; Py_buffer table_view = {NULL, NULL}; diff --git a/Objects/clinic/bytearrayobject.c.h b/Objects/clinic/bytearrayobject.c.h index b49c26b0c45..a60be76fa0c 100644 --- a/Objects/clinic/bytearrayobject.c.h +++ b/Objects/clinic/bytearrayobject.c.h @@ -39,47 +39,38 @@ bytearray_copy(PyByteArrayObject *self, PyObject *Py_UNUSED(ignored)) } PyDoc_STRVAR(bytearray_translate__doc__, -"translate(table, [deletechars])\n" +"translate($self, table, /, delete=b\'\')\n" +"--\n" +"\n" "Return a copy with each character mapped by the given translation table.\n" "\n" " table\n" " Translation table, which must be a bytes object of length 256.\n" "\n" -"All characters occurring in the optional argument deletechars are removed.\n" +"All characters occurring in the optional argument delete are removed.\n" "The remaining characters are mapped through the given translation table."); #define BYTEARRAY_TRANSLATE_METHODDEF \ - {"translate", (PyCFunction)bytearray_translate, METH_VARARGS, bytearray_translate__doc__}, + {"translate", (PyCFunction)bytearray_translate, METH_VARARGS|METH_KEYWORDS, bytearray_translate__doc__}, static PyObject * bytearray_translate_impl(PyByteArrayObject *self, PyObject *table, - int group_right_1, PyObject *deletechars); + PyObject *deletechars); static PyObject * -bytearray_translate(PyByteArrayObject *self, PyObject *args) +bytearray_translate(PyByteArrayObject *self, PyObject *args, PyObject *kwargs) { PyObject *return_value = NULL; + static const char * const _keywords[] = {"", "delete", NULL}; + static _PyArg_Parser _parser = {"O|O:translate", _keywords, 0}; PyObject *table; - int group_right_1 = 0; PyObject *deletechars = NULL; - switch (PyTuple_GET_SIZE(args)) { - case 1: - if (!PyArg_ParseTuple(args, "O:translate", &table)) { - goto exit; - } - break; - case 2: - if (!PyArg_ParseTuple(args, "OO:translate", &table, &deletechars)) { - goto exit; - } - group_right_1 = 1; - break; - default: - PyErr_SetString(PyExc_TypeError, "bytearray.translate requires 1 to 2 arguments"); - goto exit; + if (!_PyArg_ParseTupleAndKeywordsFast(args, kwargs, &_parser, + &table, &deletechars)) { + goto exit; } - return_value = bytearray_translate_impl(self, table, group_right_1, deletechars); + return_value = bytearray_translate_impl(self, table, deletechars); exit: return return_value; @@ -720,4 +711,4 @@ bytearray_sizeof(PyByteArrayObject *self, PyObject *Py_UNUSED(ignored)) { return bytearray_sizeof_impl(self); } -/*[clinic end generated code: output=0af30f8c0b1ecd76 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=59a0c86b29ff06d1 input=a9049054013a1b77]*/ diff --git a/Objects/clinic/bytesobject.c.h b/Objects/clinic/bytesobject.c.h index a99ce48ac6d..f179ce68d1e 100644 --- a/Objects/clinic/bytesobject.c.h +++ b/Objects/clinic/bytesobject.c.h @@ -269,47 +269,38 @@ exit: } PyDoc_STRVAR(bytes_translate__doc__, -"translate(table, [deletechars])\n" +"translate($self, table, /, delete=b\'\')\n" +"--\n" +"\n" "Return a copy with each character mapped by the given translation table.\n" "\n" " table\n" " Translation table, which must be a bytes object of length 256.\n" "\n" -"All characters occurring in the optional argument deletechars are removed.\n" +"All characters occurring in the optional argument delete are removed.\n" "The remaining characters are mapped through the given translation table."); #define BYTES_TRANSLATE_METHODDEF \ - {"translate", (PyCFunction)bytes_translate, METH_VARARGS, bytes_translate__doc__}, + {"translate", (PyCFunction)bytes_translate, METH_VARARGS|METH_KEYWORDS, bytes_translate__doc__}, static PyObject * -bytes_translate_impl(PyBytesObject *self, PyObject *table, int group_right_1, +bytes_translate_impl(PyBytesObject *self, PyObject *table, PyObject *deletechars); static PyObject * -bytes_translate(PyBytesObject *self, PyObject *args) +bytes_translate(PyBytesObject *self, PyObject *args, PyObject *kwargs) { PyObject *return_value = NULL; + static const char * const _keywords[] = {"", "delete", NULL}; + static _PyArg_Parser _parser = {"O|O:translate", _keywords, 0}; PyObject *table; - int group_right_1 = 0; PyObject *deletechars = NULL; - switch (PyTuple_GET_SIZE(args)) { - case 1: - if (!PyArg_ParseTuple(args, "O:translate", &table)) { - goto exit; - } - break; - case 2: - if (!PyArg_ParseTuple(args, "OO:translate", &table, &deletechars)) { - goto exit; - } - group_right_1 = 1; - break; - default: - PyErr_SetString(PyExc_TypeError, "bytes.translate requires 1 to 2 arguments"); - goto exit; + if (!_PyArg_ParseTupleAndKeywordsFast(args, kwargs, &_parser, + &table, &deletechars)) { + goto exit; } - return_value = bytes_translate_impl(self, table, group_right_1, deletechars); + return_value = bytes_translate_impl(self, table, deletechars); exit: return return_value; @@ -508,4 +499,4 @@ bytes_fromhex(PyTypeObject *type, PyObject *arg) exit: return return_value; } -/*[clinic end generated code: output=637c2c14610d3c8d input=a9049054013a1b77]*/ +/*[clinic end generated code: output=5618c05c24c1e617 input=a9049054013a1b77]*/