Skip to content

Commit abc3877

Browse files
committed
Add bytes/bytearray.maketrans() to mirror str.maketrans(), and deprecate
string.maketrans() which actually works on bytes. (Also closes python#5675.)
1 parent 78532ba commit abc3877

11 files changed

Lines changed: 134 additions & 32 deletions

File tree

Doc/library/stdtypes.rst

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@ debugging, and in numerical work.
479479
exponent.
480480

481481

482-
.. method:: float.fromhex(s)
482+
.. classmethod:: float.fromhex(s)
483483

484484
Class method to return the float represented by a hexadecimal
485485
string *s*. The string *s* may have leading and trailing
@@ -967,7 +967,7 @@ functions based on regular expressions.
967967
'example.com'
968968

969969

970-
.. method:: str.maketrans(x[, y[, z]])
970+
.. staticmethod:: str.maketrans(x[, y[, z]])
971971

972972
This static method returns a translation table usable for :meth:`str.translate`.
973973

@@ -1514,8 +1514,8 @@ Wherever one of these methods needs to interpret the bytes as characters
15141514

15151515
The bytes and bytearray types have an additional class method:
15161516

1517-
.. method:: bytes.fromhex(string)
1518-
bytearray.fromhex(string)
1517+
.. classmethod:: bytes.fromhex(string)
1518+
bytearray.fromhex(string)
15191519

15201520
This :class:`bytes` class method returns a bytes or bytearray object,
15211521
decoding the given string object. The string must contain two hexadecimal
@@ -1524,7 +1524,9 @@ The bytes and bytearray types have an additional class method:
15241524
>>> bytes.fromhex('f0 f1f2 ')
15251525
b'\xf0\xf1\xf2'
15261526

1527-
The translate method differs in semantics from the version available on strings:
1527+
1528+
The maketrans and translate methods differ in semantics from the versions
1529+
available on strings:
15281530

15291531
.. method:: bytes.translate(table[, delete])
15301532

@@ -1533,8 +1535,7 @@ The translate method differs in semantics from the version available on strings:
15331535
mapped through the given translation table, which must be a bytes object of
15341536
length 256.
15351537

1536-
You can use the :func:`string.maketrans` helper function to create a
1537-
translation table.
1538+
You can use the :func:`bytes.maketrans` method to create a translation table.
15381539

15391540
Set the *table* argument to ``None`` for translations that only delete
15401541
characters::
@@ -1543,6 +1544,16 @@ The translate method differs in semantics from the version available on strings:
15431544
b'rd ths shrt txt'
15441545

15451546

1547+
.. staticmethod:: bytes.maketrans(from, to)
1548+
1549+
This static method returns a translation table usable for
1550+
:meth:`bytes.translate` that will map each character in *from* into the
1551+
character at the same position in *to*; *from* and *to* must be bytes objects
1552+
and have the same length.
1553+
1554+
.. versionadded:: 3.1
1555+
1556+
15461557
.. _types-set:
15471558

15481559
Set Types --- :class:`set`, :class:`frozenset`
@@ -1847,7 +1858,7 @@ pairs within braces, for example: ``{'jack': 4098, 'sjoerd': 4127}`` or ``{4098:
18471858

18481859
Return a shallow copy of the dictionary.
18491860

1850-
.. method:: fromkeys(seq[, value])
1861+
.. classmethod:: fromkeys(seq[, value])
18511862

18521863
Create a new dictionary with keys from *seq* and values set to *value*.
18531864

Doc/library/string.rst

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -548,13 +548,9 @@ rule:
548548
delimiter), and it should appear last in the regular expression.
549549

550550

551-
String functions
551+
Helper functions
552552
----------------
553553

554-
The following functions are available to operate on string objects.
555-
They are not available as string methods.
556-
557-
558554
.. function:: capwords(s)
559555

560556
Split the argument into words using :func:`split`, capitalize each word using
@@ -568,3 +564,6 @@ They are not available as string methods.
568564
Return a translation table suitable for passing to :meth:`bytes.translate`,
569565
that will map each character in *from* into the character at the same
570566
position in *to*; *from* and *to* must have the same length.
567+
568+
.. deprecated:: 3.1
569+
Use the :meth:`bytes.maketrans` static method instead.

Include/bytes_methods.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ extern void _Py_bytes_title(char *result, char *s, Py_ssize_t len);
2020
extern void _Py_bytes_capitalize(char *result, char *s, Py_ssize_t len);
2121
extern void _Py_bytes_swapcase(char *result, char *s, Py_ssize_t len);
2222

23+
/* This one gets the raw argument list. */
24+
extern PyObject* _Py_bytes_maketrans(PyObject *args);
25+
2326
/* Shared __doc__ strings. */
2427
extern const char _Py_isspace__doc__[];
2528
extern const char _Py_isalpha__doc__[];
@@ -33,6 +36,7 @@ extern const char _Py_upper__doc__[];
3336
extern const char _Py_title__doc__[];
3437
extern const char _Py_capitalize__doc__[];
3538
extern const char _Py_swapcase__doc__[];
39+
extern const char _Py_maketrans__doc__[];
3640

3741
#define FLAG_LOWER 0x01
3842
#define FLAG_UPPER 0x02

Lib/string.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ def maketrans(frm: bytes, to: bytes) -> bytes:
4949
mapped to the byte at the same position in to.
5050
The strings frm and to must be of the same length.
5151
"""
52+
import warnings
53+
warnings.warn("string.maketrans is deprecated, use bytes.maketrans instead",
54+
DeprecationWarning)
5255
if len(frm) != len(to):
5356
raise ValueError("maketrans arguments must have same length")
5457
if not (isinstance(frm, bytes) and isinstance(to, bytes)):

Lib/test/test_bigmem.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -418,18 +418,15 @@ def test_title(self, size):
418418
@bigmemtest(minsize=_2G, memuse=2)
419419
def test_translate(self, size):
420420
_ = self.from_latin1
421-
trans = {
422-
ord(_('.')): _('-'),
423-
ord(_('a')): _('!'),
424-
ord(_('Z')): _('$'),
425-
}
426421
SUBSTR = _('aZz.z.Aaz.')
427-
if not isinstance(SUBSTR, str):
428-
# Workaround the inexistence of bytes.maketrans()
429-
chars = bytearray(range(256))
430-
for k, v in trans.items():
431-
chars[k] = ord(v)
432-
trans = chars
422+
if isinstance(SUBSTR, str):
423+
trans = {
424+
ord(_('.')): _('-'),
425+
ord(_('a')): _('!'),
426+
ord(_('Z')): _('$'),
427+
}
428+
else:
429+
trans = bytes.maketrans(b'.aZ', b'-!$')
433430
sublen = len(SUBSTR)
434431
repeats = size // sublen + 2
435432
s = SUBSTR * repeats

Lib/test/test_bytes.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,13 @@ def test_ord(self):
450450
self.assertEqual([ord(b[i:i+1]) for i in range(len(b))],
451451
[0, 65, 127, 128, 255])
452452

453+
def test_maketrans(self):
454+
transtable = b'\000\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037 !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`xyzdefghijklmnopqrstuvwxyz{|}~\177\200\201\202\203\204\205\206\207\210\211\212\213\214\215\216\217\220\221\222\223\224\225\226\227\230\231\232\233\234\235\236\237\240\241\242\243\244\245\246\247\250\251\252\253\254\255\256\257\260\261\262\263\264\265\266\267\270\271\272\273\274\275\276\277\300\301\302\303\304\305\306\307\310\311\312\313\314\315\316\317\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337\340\341\342\343\344\345\346\347\350\351\352\353\354\355\356\357\360\361\362\363\364\365\366\367\370\371\372\373\374\375\376\377'
455+
456+
self.assertEqual(self.type2test.maketrans(b'abc', b'xyz'), transtable)
457+
self.assertRaises(ValueError, self.type2test.maketrans, b'abc', b'xyzq')
458+
self.assertRaises(TypeError, self.type2test.maketrans, 'abc', 'def')
459+
453460

454461
class BytesTest(BaseBytesTest):
455462
type2test = bytes

Lib/test/test_string.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -101,14 +101,6 @@ def check_unused_args(self, used_args, args, kwargs):
101101
self.assertRaises(ValueError, fmt.format, "{0}", 10, 20, i=100)
102102
self.assertRaises(ValueError, fmt.format, "{i}", 10, 20, i=100)
103103

104-
105-
def test_maketrans(self):
106-
transtable = b'\000\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037 !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`xyzdefghijklmnopqrstuvwxyz{|}~\177\200\201\202\203\204\205\206\207\210\211\212\213\214\215\216\217\220\221\222\223\224\225\226\227\230\231\232\233\234\235\236\237\240\241\242\243\244\245\246\247\250\251\252\253\254\255\256\257\260\261\262\263\264\265\266\267\270\271\272\273\274\275\276\277\300\301\302\303\304\305\306\307\310\311\312\313\314\315\316\317\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337\340\341\342\343\344\345\346\347\350\351\352\353\354\355\356\357\360\361\362\363\364\365\366\367\370\371\372\373\374\375\376\377'
107-
108-
self.assertEqual(string.maketrans(b'abc', b'xyz'), transtable)
109-
self.assertRaises(ValueError, string.maketrans, b'abc', b'xyzq')
110-
self.assertRaises(TypeError, string.maketrans, 'abc', 'def')
111-
112104
def test_main():
113105
support.run_unittest(ModuleTest)
114106

Misc/NEWS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ What's New in Python 3.1 beta 1?
1212
Core and Builtins
1313
-----------------
1414

15+
- The string.maketrans() function is deprecated; there is a new static method
16+
maketrans() on the bytes and bytearray classes. This removes confusion about
17+
the types string.maketrans() is supposed to work with, and mirrors the
18+
methods available on the str class.
19+
1520
- Issue #2170: refactored xml.dom.minidom.normalize, increasing both
1621
its clarity and its speed.
1722

Objects/bytearrayobject.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1451,6 +1451,13 @@ bytes_translate(PyByteArrayObject *self, PyObject *args)
14511451
}
14521452

14531453

1454+
static PyObject *
1455+
bytes_maketrans(PyObject *null, PyObject *args)
1456+
{
1457+
return _Py_bytes_maketrans(args);
1458+
}
1459+
1460+
14541461
#define FORWARD 1
14551462
#define REVERSE -1
14561463

@@ -3131,6 +3138,8 @@ bytes_methods[] = {
31313138
{"ljust", (PyCFunction)stringlib_ljust, METH_VARARGS, ljust__doc__},
31323139
{"lower", (PyCFunction)stringlib_lower, METH_NOARGS, _Py_lower__doc__},
31333140
{"lstrip", (PyCFunction)bytes_lstrip, METH_VARARGS, lstrip__doc__},
3141+
{"maketrans", (PyCFunction)bytes_maketrans, METH_VARARGS|METH_STATIC,
3142+
_Py_maketrans__doc__},
31343143
{"partition", (PyCFunction)bytes_partition, METH_O, partition__doc__},
31353144
{"pop", (PyCFunction)bytes_pop, METH_VARARGS, pop__doc__},
31363145
{"remove", (PyCFunction)bytes_remove, METH_O, remove__doc__},

Objects/bytes_methods.c

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,3 +608,70 @@ _Py_bytes_swapcase(char *result, char *s, Py_ssize_t len)
608608
}
609609
}
610610

611+
612+
PyDoc_STRVAR_shared(_Py_maketrans__doc__,
613+
"B.maketrans(frm, to) -> translation table\n\
614+
\n\
615+
Return a translation table (a bytes object of length 256)\n\
616+
suitable for use in bytes.translate where each byte in frm is\n\
617+
mapped to the byte at the same position in to.\n\
618+
The strings frm and to must be of the same length.");
619+
620+
static Py_ssize_t
621+
_getbuffer(PyObject *obj, Py_buffer *view)
622+
{
623+
PyBufferProcs *buffer = Py_TYPE(obj)->tp_as_buffer;
624+
625+
if (buffer == NULL || buffer->bf_getbuffer == NULL)
626+
{
627+
PyErr_Format(PyExc_TypeError,
628+
"Type %.100s doesn't support the buffer API",
629+
Py_TYPE(obj)->tp_name);
630+
return -1;
631+
}
632+
633+
if (buffer->bf_getbuffer(obj, view, PyBUF_SIMPLE) < 0)
634+
return -1;
635+
return view->len;
636+
}
637+
638+
PyObject *
639+
_Py_bytes_maketrans(PyObject *args)
640+
{
641+
PyObject *frm, *to, *res = NULL;
642+
Py_buffer bfrm, bto;
643+
int i;
644+
char *p;
645+
646+
bfrm.len = -1;
647+
bto.len = -1;
648+
649+
if (!PyArg_ParseTuple(args, "OO:maketrans", &frm, &to))
650+
return NULL;
651+
if (_getbuffer(frm, &bfrm) < 0)
652+
return NULL;
653+
if (_getbuffer(to, &bto) < 0)
654+
goto done;
655+
if (bfrm.len != bto.len) {
656+
PyErr_Format(PyExc_ValueError,
657+
"maketrans arguments must have same length");
658+
goto done;
659+
}
660+
res = PyBytes_FromStringAndSize(NULL, 256);
661+
if (!res) {
662+
goto done;
663+
}
664+
p = PyBytes_AS_STRING(res);
665+
for (i = 0; i < 256; i++)
666+
p[i] = i;
667+
for (i = 0; i < bfrm.len; i++) {
668+
p[(int)((char *)bfrm.buf)[i]] = ((char *)bto.buf)[i];
669+
}
670+
671+
done:
672+
if (bfrm.len != -1)
673+
PyBuffer_Release(&bfrm);
674+
if (bto.len != -1)
675+
PyBuffer_Release(&bto);
676+
return res;
677+
}

0 commit comments

Comments
 (0)