Skip to content

Commit ac65d96

Browse files
committed
Issue #12170: The count(), find(), rfind(), index() and rindex() methods
of bytes and bytearray objects now accept an integer between 0 and 255 as their first argument. Patch by Petri Lehtinen.
1 parent 407cfd1 commit ac65d96

File tree

7 files changed

+261
-51
lines changed

7 files changed

+261
-51
lines changed

Doc/library/stdtypes.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1805,6 +1805,12 @@ the objects to strings, they have a :func:`decode` method.
18051805
Wherever one of these methods needs to interpret the bytes as characters
18061806
(e.g. the :func:`is...` methods), the ASCII character set is assumed.
18071807

1808+
.. versionadded:: 3.3
1809+
The functions :func:`count`, :func:`find`, :func:`index`,
1810+
:func:`rfind` and :func:`rindex` have additional semantics compared to
1811+
the corresponding string functions: They also accept an integer in
1812+
range 0 to 255 (a byte) as their first argument.
1813+
18081814
.. note::
18091815

18101816
The methods on bytes and bytearray objects don't accept strings as their

Lib/test/string_tests.py

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ class BaseTest(unittest.TestCase):
2828
# Change in subclasses to change the behaviour of fixtesttype()
2929
type2test = None
3030

31+
# Whether the "contained items" of the container are integers in
32+
# range(0, 256) (i.e. bytes, bytearray) or strings of length 1
33+
# (str)
34+
contains_bytes = False
35+
3136
# All tests pass their arguments to the testing methods
3237
# as str objects. fixtesttype() can be used to propagate
3338
# these arguments to the appropriate type
@@ -117,7 +122,11 @@ def test_count(self):
117122
self.checkequal(0, '', 'count', 'xx', sys.maxsize, 0)
118123

119124
self.checkraises(TypeError, 'hello', 'count')
120-
self.checkraises(TypeError, 'hello', 'count', 42)
125+
126+
if self.contains_bytes:
127+
self.checkequal(0, 'hello', 'count', 42)
128+
else:
129+
self.checkraises(TypeError, 'hello', 'count', 42)
121130

122131
# For a variety of combinations,
123132
# verify that str.count() matches an equivalent function
@@ -163,7 +172,11 @@ def test_find(self):
163172
self.checkequal( 2, 'rrarrrrrrrrra', 'find', 'a', None, 6)
164173

165174
self.checkraises(TypeError, 'hello', 'find')
166-
self.checkraises(TypeError, 'hello', 'find', 42)
175+
176+
if self.contains_bytes:
177+
self.checkequal(-1, 'hello', 'find', 42)
178+
else:
179+
self.checkraises(TypeError, 'hello', 'find', 42)
167180

168181
self.checkequal(0, '', 'find', '')
169182
self.checkequal(-1, '', 'find', '', 1, 1)
@@ -217,7 +230,11 @@ def test_rfind(self):
217230
self.checkequal( 2, 'rrarrrrrrrrra', 'rfind', 'a', None, 6)
218231

219232
self.checkraises(TypeError, 'hello', 'rfind')
220-
self.checkraises(TypeError, 'hello', 'rfind', 42)
233+
234+
if self.contains_bytes:
235+
self.checkequal(-1, 'hello', 'rfind', 42)
236+
else:
237+
self.checkraises(TypeError, 'hello', 'rfind', 42)
221238

222239
# For a variety of combinations,
223240
# verify that str.rfind() matches __contains__
@@ -264,7 +281,11 @@ def test_index(self):
264281
self.checkequal( 2, 'rrarrrrrrrrra', 'index', 'a', None, 6)
265282

266283
self.checkraises(TypeError, 'hello', 'index')
267-
self.checkraises(TypeError, 'hello', 'index', 42)
284+
285+
if self.contains_bytes:
286+
self.checkraises(ValueError, 'hello', 'index', 42)
287+
else:
288+
self.checkraises(TypeError, 'hello', 'index', 42)
268289

269290
def test_rindex(self):
270291
self.checkequal(12, 'abcdefghiabc', 'rindex', '')
@@ -286,7 +307,11 @@ def test_rindex(self):
286307
self.checkequal( 2, 'rrarrrrrrrrra', 'rindex', 'a', None, 6)
287308

288309
self.checkraises(TypeError, 'hello', 'rindex')
289-
self.checkraises(TypeError, 'hello', 'rindex', 42)
310+
311+
if self.contains_bytes:
312+
self.checkraises(ValueError, 'hello', 'rindex', 42)
313+
else:
314+
self.checkraises(TypeError, 'hello', 'rindex', 42)
290315

291316
def test_lower(self):
292317
self.checkequal('hello', 'HeLLo', 'lower')

Lib/test/test_bytes.py

Lines changed: 88 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -293,10 +293,27 @@ def test_join(self):
293293

294294
def test_count(self):
295295
b = self.type2test(b'mississippi')
296+
i = 105
297+
p = 112
298+
w = 119
299+
296300
self.assertEqual(b.count(b'i'), 4)
297301
self.assertEqual(b.count(b'ss'), 2)
298302
self.assertEqual(b.count(b'w'), 0)
299303

304+
self.assertEqual(b.count(i), 4)
305+
self.assertEqual(b.count(w), 0)
306+
307+
self.assertEqual(b.count(b'i', 6), 2)
308+
self.assertEqual(b.count(b'p', 6), 2)
309+
self.assertEqual(b.count(b'i', 1, 3), 1)
310+
self.assertEqual(b.count(b'p', 7, 9), 1)
311+
312+
self.assertEqual(b.count(i, 6), 2)
313+
self.assertEqual(b.count(p, 6), 2)
314+
self.assertEqual(b.count(i, 1, 3), 1)
315+
self.assertEqual(b.count(p, 7, 9), 1)
316+
300317
def test_startswith(self):
301318
b = self.type2test(b'hello')
302319
self.assertFalse(self.type2test().startswith(b"anything"))
@@ -327,35 +344,81 @@ def test_endswith(self):
327344

328345
def test_find(self):
329346
b = self.type2test(b'mississippi')
347+
i = 105
348+
w = 119
349+
330350
self.assertEqual(b.find(b'ss'), 2)
351+
self.assertEqual(b.find(b'w'), -1)
352+
self.assertEqual(b.find(b'mississippian'), -1)
353+
354+
self.assertEqual(b.find(i), 1)
355+
self.assertEqual(b.find(w), -1)
356+
331357
self.assertEqual(b.find(b'ss', 3), 5)
332358
self.assertEqual(b.find(b'ss', 1, 7), 2)
333359
self.assertEqual(b.find(b'ss', 1, 3), -1)
334-
self.assertEqual(b.find(b'w'), -1)
335-
self.assertEqual(b.find(b'mississippian'), -1)
360+
361+
self.assertEqual(b.find(i, 6), 7)
362+
self.assertEqual(b.find(i, 1, 3), 1)
363+
self.assertEqual(b.find(w, 1, 3), -1)
336364

337365
def test_rfind(self):
338366
b = self.type2test(b'mississippi')
367+
i = 105
368+
w = 119
369+
339370
self.assertEqual(b.rfind(b'ss'), 5)
340-
self.assertEqual(b.rfind(b'ss', 3), 5)
341-
self.assertEqual(b.rfind(b'ss', 0, 6), 2)
342371
self.assertEqual(b.rfind(b'w'), -1)
343372
self.assertEqual(b.rfind(b'mississippian'), -1)
344373

374+
self.assertEqual(b.rfind(i), 10)
375+
self.assertEqual(b.rfind(w), -1)
376+
377+
self.assertEqual(b.rfind(b'ss', 3), 5)
378+
self.assertEqual(b.rfind(b'ss', 0, 6), 2)
379+
380+
self.assertEqual(b.rfind(i, 1, 3), 1)
381+
self.assertEqual(b.rfind(i, 3, 9), 7)
382+
self.assertEqual(b.rfind(w, 1, 3), -1)
383+
345384
def test_index(self):
346-
b = self.type2test(b'world')
347-
self.assertEqual(b.index(b'w'), 0)
348-
self.assertEqual(b.index(b'orl'), 1)
349-
self.assertRaises(ValueError, b.index, b'worm')
350-
self.assertRaises(ValueError, b.index, b'ldo')
385+
b = self.type2test(b'mississippi')
386+
i = 105
387+
w = 119
388+
389+
self.assertEqual(b.index(b'ss'), 2)
390+
self.assertRaises(ValueError, b.index, b'w')
391+
self.assertRaises(ValueError, b.index, b'mississippian')
392+
393+
self.assertEqual(b.index(i), 1)
394+
self.assertRaises(ValueError, b.index, w)
395+
396+
self.assertEqual(b.index(b'ss', 3), 5)
397+
self.assertEqual(b.index(b'ss', 1, 7), 2)
398+
self.assertRaises(ValueError, b.index, b'ss', 1, 3)
399+
400+
self.assertEqual(b.index(i, 6), 7)
401+
self.assertEqual(b.index(i, 1, 3), 1)
402+
self.assertRaises(ValueError, b.index, w, 1, 3)
351403

352404
def test_rindex(self):
353-
# XXX could be more rigorous
354-
b = self.type2test(b'world')
355-
self.assertEqual(b.rindex(b'w'), 0)
356-
self.assertEqual(b.rindex(b'orl'), 1)
357-
self.assertRaises(ValueError, b.rindex, b'worm')
358-
self.assertRaises(ValueError, b.rindex, b'ldo')
405+
b = self.type2test(b'mississippi')
406+
i = 105
407+
w = 119
408+
409+
self.assertEqual(b.rindex(b'ss'), 5)
410+
self.assertRaises(ValueError, b.rindex, b'w')
411+
self.assertRaises(ValueError, b.rindex, b'mississippian')
412+
413+
self.assertEqual(b.rindex(i), 10)
414+
self.assertRaises(ValueError, b.rindex, w)
415+
416+
self.assertEqual(b.rindex(b'ss', 3), 5)
417+
self.assertEqual(b.rindex(b'ss', 0, 6), 2)
418+
419+
self.assertEqual(b.rindex(i, 1, 3), 1)
420+
self.assertEqual(b.rindex(i, 3, 9), 7)
421+
self.assertRaises(ValueError, b.rindex, w, 1, 3)
359422

360423
def test_replace(self):
361424
b = self.type2test(b'mississippi')
@@ -552,6 +615,14 @@ def test_none_arguments(self):
552615
self.assertEqual(True, b.startswith(h, None, -2))
553616
self.assertEqual(False, b.startswith(x, None, None))
554617

618+
def test_integer_arguments_out_of_byte_range(self):
619+
b = self.type2test(b'hello')
620+
621+
for method in (b.count, b.find, b.index, b.rfind, b.rindex):
622+
self.assertRaises(ValueError, method, -1)
623+
self.assertRaises(ValueError, method, 256)
624+
self.assertRaises(ValueError, method, 9999)
625+
555626
def test_find_etc_raise_correct_error_messages(self):
556627
# issue 11828
557628
b = self.type2test(b'hello')
@@ -1161,9 +1232,11 @@ def test_lower(self):
11611232

11621233
class ByteArrayAsStringTest(FixedStringTest):
11631234
type2test = bytearray
1235+
contains_bytes = True
11641236

11651237
class BytesAsStringTest(FixedStringTest):
11661238
type2test = bytes
1239+
contains_bytes = True
11671240

11681241

11691242
class SubclassTest(unittest.TestCase):

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ What's New in Python 3.3 Alpha 1?
1010
Core and Builtins
1111
-----------------
1212

13+
- Issue #12170: The count(), find(), rfind(), index() and rindex() methods
14+
of bytes and bytearray objects now accept an integer between 0 and 255
15+
as their first argument. Patch by Petri Lehtinen.
16+
1317
- Issue #12604: VTRACE macro expanded to no-op in _sre.c to avoid compiler
1418
warnings. Patch by Josh Triplett and Petri Lehtinen.
1519

Objects/bytearrayobject.c

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,24 +1071,41 @@ Py_LOCAL_INLINE(Py_ssize_t)
10711071
bytearray_find_internal(PyByteArrayObject *self, PyObject *args, int dir)
10721072
{
10731073
PyObject *subobj;
1074+
char byte;
10741075
Py_buffer subbuf;
1076+
const char *sub;
1077+
Py_ssize_t sub_len;
10751078
Py_ssize_t start=0, end=PY_SSIZE_T_MAX;
10761079
Py_ssize_t res;
10771080

1078-
if (!stringlib_parse_args_finds("find/rfind/index/rindex",
1079-
args, &subobj, &start, &end))
1080-
return -2;
1081-
if (_getbuffer(subobj, &subbuf) < 0)
1081+
if (!stringlib_parse_args_finds_byte("find/rfind/index/rindex",
1082+
args, &subobj, &byte, &start, &end))
10821083
return -2;
1084+
1085+
if (subobj) {
1086+
if (_getbuffer(subobj, &subbuf) < 0)
1087+
return -2;
1088+
1089+
sub = subbuf.buf;
1090+
sub_len = subbuf.len;
1091+
}
1092+
else {
1093+
sub = &byte;
1094+
sub_len = 1;
1095+
}
1096+
10831097
if (dir > 0)
10841098
res = stringlib_find_slice(
10851099
PyByteArray_AS_STRING(self), PyByteArray_GET_SIZE(self),
1086-
subbuf.buf, subbuf.len, start, end);
1100+
sub, sub_len, start, end);
10871101
else
10881102
res = stringlib_rfind_slice(
10891103
PyByteArray_AS_STRING(self), PyByteArray_GET_SIZE(self),
1090-
subbuf.buf, subbuf.len, start, end);
1091-
PyBuffer_Release(&subbuf);
1104+
sub, sub_len, start, end);
1105+
1106+
if (subobj)
1107+
PyBuffer_Release(&subbuf);
1108+
10921109
return res;
10931110
}
10941111

@@ -1121,23 +1138,39 @@ static PyObject *
11211138
bytearray_count(PyByteArrayObject *self, PyObject *args)
11221139
{
11231140
PyObject *sub_obj;
1124-
const char *str = PyByteArray_AS_STRING(self);
1141+
const char *str = PyByteArray_AS_STRING(self), *sub;
1142+
Py_ssize_t sub_len;
1143+
char byte;
11251144
Py_ssize_t start = 0, end = PY_SSIZE_T_MAX;
1145+
11261146
Py_buffer vsub;
11271147
PyObject *count_obj;
11281148

1129-
if (!stringlib_parse_args_finds("count", args, &sub_obj, &start, &end))
1149+
if (!stringlib_parse_args_finds_byte("count", args, &sub_obj, &byte,
1150+
&start, &end))
11301151
return NULL;
11311152

1132-
if (_getbuffer(sub_obj, &vsub) < 0)
1133-
return NULL;
1153+
if (sub_obj) {
1154+
if (_getbuffer(sub_obj, &vsub) < 0)
1155+
return NULL;
1156+
1157+
sub = vsub.buf;
1158+
sub_len = vsub.len;
1159+
}
1160+
else {
1161+
sub = &byte;
1162+
sub_len = 1;
1163+
}
11341164

11351165
ADJUST_INDICES(start, end, PyByteArray_GET_SIZE(self));
11361166

11371167
count_obj = PyLong_FromSsize_t(
1138-
stringlib_count(str + start, end - start, vsub.buf, vsub.len, PY_SSIZE_T_MAX)
1168+
stringlib_count(str + start, end - start, sub, sub_len, PY_SSIZE_T_MAX)
11391169
);
1140-
PyBuffer_Release(&vsub);
1170+
1171+
if (sub_obj)
1172+
PyBuffer_Release(&vsub);
1173+
11411174
return count_obj;
11421175
}
11431176

0 commit comments

Comments
 (0)