Skip to content

Commit ceee077

Browse files
committed
python#1496: revert str.translate() to the old version, and add
str.maketrans() to make a table in a more comfortable way.
1 parent 45f9af3 commit ceee077

3 files changed

Lines changed: 155 additions & 61 deletions

File tree

Doc/library/stdtypes.rst

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,21 @@ functions based on regular expressions.
800800
'example.com'
801801

802802

803+
.. method:: str.maketrans(x[, y[, z]])
804+
805+
This static method returns a translation table usable for :meth:`str.translate`.
806+
807+
If there is only one argument, it must be a dictionary mapping Unicode
808+
ordinals (integers) or characters (strings of length 1) to Unicode ordinals,
809+
strings (of arbitrary lengths) or None. Character keys will then be
810+
converted to ordinals.
811+
812+
If there are two arguments, they must be strings of equal length, and in the
813+
resulting dictionary, each character in x will be mapped to the character at
814+
the same position in y. If there is a third argument, it must be a string,
815+
whose characters will be mapped to None in the result.
816+
817+
803818
.. method:: str.partition(sep)
804819

805820
Split the string at the first occurrence of *sep*, and return a 3-tuple
@@ -934,15 +949,17 @@ functions based on regular expressions.
934949
.. method:: str.translate(map)
935950

936951
Return a copy of the *s* where all characters have been mapped through the
937-
*map* which must be a dictionary of characters (strings of length 1) or
938-
Unicode ordinals (integers) to Unicode ordinals, strings or ``None``.
939-
Unmapped characters are left untouched. Characters mapped to ``None`` are
940-
deleted.
952+
*map* which must be a dictionary of Unicode ordinals(integers) to Unicode
953+
ordinals, strings or ``None``. Unmapped characters are left untouched.
954+
Characters mapped to ``None`` are deleted.
955+
956+
A *map* for :meth:`translate` is usually best created by
957+
:meth:`str.maketrans`.
941958

942959
.. note::
943960

944-
A more flexible approach is to create a custom character mapping codec
945-
using the :mod:`codecs` module (see :mod:`encodings.cp1251` for an
961+
An even more flexible approach is to create a custom character mapping
962+
codec using the :mod:`codecs` module (see :mod:`encodings.cp1251` for an
946963
example).
947964

948965

Lib/test/test_unicode.py

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -166,18 +166,37 @@ def test_rindex(self):
166166
self.assertRaises(ValueError, 'abcdefghi'.rindex, 'ghi', 0, 8)
167167
self.assertRaises(ValueError, 'abcdefghi'.rindex, 'ghi', 0, -1)
168168

169-
def test_translate(self):
170-
self.checkequalnofix('bbbc', 'abababc', 'translate', {ord('a'):None})
171-
self.checkequalnofix('iiic', 'abababc', 'translate', {ord('a'):None, ord('b'):ord('i')})
172-
self.checkequalnofix('iiix', 'abababc', 'translate', {ord('a'):None, ord('b'):ord('i'), ord('c'):'x'})
173-
self.checkequalnofix('<i><i><i>c', 'abababc', 'translate', {'a':None, 'b':'<i>'})
174-
self.checkequalnofix('c', 'abababc', 'translate', {ord('a'):None, ord('b'):''})
175-
self.checkequalnofix('xyyx', 'xzx', 'translate', {ord('z'):'yy'})
169+
def test_maketrans_translate(self):
170+
# these work with plain translate()
171+
self.checkequalnofix('bbbc', 'abababc', 'translate',
172+
{ord('a'): None})
173+
self.checkequalnofix('iiic', 'abababc', 'translate',
174+
{ord('a'): None, ord('b'): ord('i')})
175+
self.checkequalnofix('iiix', 'abababc', 'translate',
176+
{ord('a'): None, ord('b'): ord('i'), ord('c'): 'x'})
177+
self.checkequalnofix('c', 'abababc', 'translate',
178+
{ord('a'): None, ord('b'): ''})
179+
self.checkequalnofix('xyyx', 'xzx', 'translate',
180+
{ord('z'): 'yy'})
181+
# this needs maketrans()
182+
self.checkequalnofix('abababc', 'abababc', 'translate',
183+
{'b': '<i>'})
184+
tbl = self.type2test.maketrans({'a': None, 'b': '<i>'})
185+
self.checkequalnofix('<i><i><i>c', 'abababc', 'translate', tbl)
186+
# test alternative way of calling maketrans()
187+
tbl = self.type2test.maketrans('abc', 'xyz', 'd')
188+
self.checkequalnofix('xyzzy', 'abdcdcbdddd', 'translate', tbl)
189+
190+
self.assertRaises(TypeError, self.type2test.maketrans)
191+
self.assertRaises(ValueError, self.type2test.maketrans, 'abc', 'defg')
192+
self.assertRaises(TypeError, self.type2test.maketrans, 2, 'def')
193+
self.assertRaises(TypeError, self.type2test.maketrans, 'abc', 2)
194+
self.assertRaises(TypeError, self.type2test.maketrans, 'abc', 'def', 2)
195+
self.assertRaises(ValueError, self.type2test.maketrans, {'xy': 2})
196+
self.assertRaises(TypeError, self.type2test.maketrans, {(1,): 2})
176197

177198
self.assertRaises(TypeError, 'hello'.translate)
178199
self.assertRaises(TypeError, 'abababc'.translate, 'abc', 'xyz')
179-
self.assertRaises(ValueError, 'abababc'.translate, {'xy':2})
180-
self.assertRaises(TypeError, 'abababc'.translate, {(1,):2})
181200

182201
def test_split(self):
183202
string_tests.CommonTest.test_split(self)

Objects/unicodeobject.c

Lines changed: 104 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -7793,68 +7793,124 @@ unicode_swapcase(PyUnicodeObject *self)
77937793
return fixup(self, fixswapcase);
77947794
}
77957795

7796-
PyDoc_STRVAR(translate__doc__,
7797-
"S.translate(table) -> unicode\n\
7796+
PyDoc_STRVAR(maketrans__doc__,
7797+
"str.maketrans(x[, y[, z]]) -> dict (static method)\n\
77987798
\n\
7799-
Return a copy of the string S, where all characters have been mapped\n\
7800-
through the given translation table, which must be a mapping of\n\
7801-
Unicode ordinals to Unicode ordinals, Unicode strings or None.\n\
7802-
Unmapped characters are left untouched. Characters mapped to None\n\
7803-
are deleted.");
7799+
Return a translation table usable for str.translate().\n\
7800+
If there is only one argument, it must be a dictionary mapping Unicode\n\
7801+
ordinals (integers) or characters to Unicode ordinals, strings or None.\n\
7802+
Character keys will then be converted to ordinals.\n\
7803+
If there are two arguments, they must be strings of equal length, and\n\
7804+
in the resulting dictionary, each character in x will be mapped to the\n\
7805+
character at the same position in y. If there is a third argument, it\n\
7806+
must be a string, whose characters will be mapped to None in the result.");
78047807

78057808
static PyObject*
7806-
unicode_translate(PyUnicodeObject *self, PyObject *table)
7809+
unicode_maketrans(PyUnicodeObject *null, PyObject *args)
78077810
{
7808-
PyObject *newtable = NULL;
7811+
PyObject *x, *y = NULL, *z = NULL;
7812+
PyObject *new = NULL, *key, *value;
78097813
Py_ssize_t i = 0;
7810-
PyObject *key, *value, *result;
7811-
7812-
if (!PyDict_Check(table)) {
7813-
PyErr_SetString(PyExc_TypeError, "translate argument must be a dict");
7814+
int res;
7815+
7816+
if (!PyArg_ParseTuple(args, "O|UU:maketrans", &x, &y, &z))
78147817
return NULL;
7815-
}
7816-
/* fixup the table -- allow size-1 string keys instead of only int keys */
7817-
newtable = PyDict_Copy(table);
7818-
if (!newtable) return NULL;
7819-
while (PyDict_Next(table, &i, &key, &value)) {
7820-
if (PyUnicode_Check(key)) {
7821-
/* convert string keys to integer keys */
7822-
PyObject *newkey;
7823-
int res;
7824-
if (PyUnicode_GET_SIZE(key) != 1) {
7825-
PyErr_SetString(PyExc_ValueError, "string items in translate "
7826-
"table must be 1 element long");
7827-
goto err;
7828-
}
7829-
newkey = PyInt_FromLong(PyUnicode_AS_UNICODE(key)[0]);
7830-
if (!newkey)
7818+
new = PyDict_New();
7819+
if (!new)
7820+
return NULL;
7821+
if (y != NULL) {
7822+
/* x must be a string too, of equal length */
7823+
Py_ssize_t ylen = PyUnicode_GET_SIZE(y);
7824+
if (!PyUnicode_Check(x)) {
7825+
PyErr_SetString(PyExc_TypeError, "first maketrans argument must "
7826+
"be a string if there is a second argument");
7827+
goto err;
7828+
}
7829+
if (PyUnicode_GET_SIZE(x) != ylen) {
7830+
PyErr_SetString(PyExc_ValueError, "the first two maketrans "
7831+
"arguments must have equal length");
7832+
goto err;
7833+
}
7834+
/* create entries for translating chars in x to those in y */
7835+
for (i = 0; i < PyUnicode_GET_SIZE(x); i++) {
7836+
key = PyInt_FromLong(PyUnicode_AS_UNICODE(x)[i]);
7837+
value = PyInt_FromLong(PyUnicode_AS_UNICODE(y)[i]);
7838+
if (!key || !value)
78317839
goto err;
7832-
res = PyDict_SetItem(newtable, newkey, value);
7833-
Py_DECREF(newkey);
7840+
res = PyDict_SetItem(new, key, value);
7841+
Py_DECREF(key);
7842+
Py_DECREF(value);
78347843
if (res < 0)
78357844
goto err;
7836-
} else if (PyInt_Check(key)) {
7837-
/* just keep integer keys */
7838-
if (PyDict_SetItem(newtable, key, value) < 0)
7839-
goto err;
7840-
} else {
7841-
PyErr_SetString(PyExc_TypeError, "items in translate table must be "
7842-
"strings or integers");
7845+
}
7846+
/* create entries for deleting chars in z */
7847+
if (z != NULL) {
7848+
for (i = 0; i < PyUnicode_GET_SIZE(z); i++) {
7849+
key = PyInt_FromLong(PyUnicode_AS_UNICODE(z)[i]);
7850+
if (!key)
7851+
goto err;
7852+
res = PyDict_SetItem(new, key, Py_None);
7853+
Py_DECREF(key);
7854+
if (res < 0)
7855+
goto err;
7856+
}
7857+
}
7858+
} else {
7859+
/* x must be a dict */
7860+
if (!PyDict_Check(x)) {
7861+
PyErr_SetString(PyExc_TypeError, "if you give only one argument "
7862+
"to maketrans it must be a dict");
78437863
goto err;
78447864
}
7865+
/* copy entries into the new dict, converting string keys to int keys */
7866+
while (PyDict_Next(x, &i, &key, &value)) {
7867+
if (PyUnicode_Check(key)) {
7868+
/* convert string keys to integer keys */
7869+
PyObject *newkey;
7870+
if (PyUnicode_GET_SIZE(key) != 1) {
7871+
PyErr_SetString(PyExc_ValueError, "string keys in translate "
7872+
"table must be of length 1");
7873+
goto err;
7874+
}
7875+
newkey = PyInt_FromLong(PyUnicode_AS_UNICODE(key)[0]);
7876+
if (!newkey)
7877+
goto err;
7878+
res = PyDict_SetItem(new, newkey, value);
7879+
Py_DECREF(newkey);
7880+
if (res < 0)
7881+
goto err;
7882+
} else if (PyInt_Check(key)) {
7883+
/* just keep integer keys */
7884+
if (PyDict_SetItem(new, key, value) < 0)
7885+
goto err;
7886+
} else {
7887+
PyErr_SetString(PyExc_TypeError, "keys in translate table must "
7888+
"be strings or integers");
7889+
goto err;
7890+
}
7891+
}
78457892
}
7846-
7847-
result = PyUnicode_TranslateCharmap(self->str,
7848-
self->length,
7849-
newtable,
7850-
"ignore");
7851-
Py_DECREF(newtable);
7852-
return result;
7893+
return new;
78537894
err:
7854-
Py_DECREF(newtable);
7895+
Py_DECREF(new);
78557896
return NULL;
78567897
}
78577898

7899+
PyDoc_STRVAR(translate__doc__,
7900+
"S.translate(table) -> unicode\n\
7901+
\n\
7902+
Return a copy of the string S, where all characters have been mapped\n\
7903+
through the given translation table, which must be a mapping of\n\
7904+
Unicode ordinals to Unicode ordinals, Unicode strings or None.\n\
7905+
Unmapped characters are left untouched. Characters mapped to None\n\
7906+
are deleted.");
7907+
7908+
static PyObject*
7909+
unicode_translate(PyUnicodeObject *self, PyObject *table)
7910+
{
7911+
return PyUnicode_TranslateCharmap(self->str, self->length, table, "ignore");
7912+
}
7913+
78587914
PyDoc_STRVAR(upper__doc__,
78597915
"S.upper() -> unicode\n\
78607916
\n\
@@ -8076,6 +8132,8 @@ static PyMethodDef unicode_methods[] = {
80768132
{"__format__", (PyCFunction) unicode_unicode__format__, METH_VARARGS, p_format__doc__},
80778133
{"_formatter_field_name_split", (PyCFunction) formatter_field_name_split, METH_NOARGS},
80788134
{"_formatter_parser", (PyCFunction) formatter_parser, METH_NOARGS},
8135+
{"maketrans", (PyCFunction) unicode_maketrans,
8136+
METH_VARARGS | METH_STATIC, maketrans__doc__},
80798137
#if 0
80808138
{"capwords", (PyCFunction) unicode_capwords, METH_NOARGS, capwords__doc__},
80818139
#endif

0 commit comments

Comments
 (0)