Skip to content
62 changes: 50 additions & 12 deletions numba/cpython/unicode.py
Original file line number Diff line number Diff line change
Expand Up @@ -813,18 +813,6 @@ def impl(data, sep):
return impl


@overload_method(types.UnicodeType, 'startswith')
def unicode_startswith(a, b):
if isinstance(b, types.UnicodeType):
def startswith_impl(a, b):
return _cmp_region(a, 0, b, 0, len(b)) == 0
return startswith_impl
if isinstance(b, types.UnicodeCharSeq):
def startswith_impl(a, b):
return a.startswith(str(b))
return startswith_impl


# https://github.com/python/cpython/blob/201c8f79450628241574fba940e08107178dc3a5/Objects/unicodeobject.c#L9342-L9354 # noqa: E501
@register_jitable
def _adjust_indices(length, start, end):
Expand All @@ -842,6 +830,56 @@ def _adjust_indices(length, start, end):
return start, end


@overload_method(types.UnicodeType, 'startswith')
def unicode_startswith(s, substr, start=None, end=None):
Comment thread
dlee992 marked this conversation as resolved.
Resolved
if not (start is None or isinstance(start, (types.Omitted,
Comment thread
dlee992 marked this conversation as resolved.
Resolved
types.Integer,
types.NoneType))):
raise TypingError('The arg must be a Integer or None')
Comment thread
dlee992 marked this conversation as resolved.
Resolved

if not (end is None or isinstance(end, (types.Omitted,
types.Integer,
types.NoneType))):
raise TypingError('The arg must be a Integer or None')
Comment thread
dlee992 marked this conversation as resolved.
Resolved

if isinstance(substr, (types.Tuple, types.UniTuple)):
def startswith_tuple_impl(s, substr, start=None, end=None):
for item in substr:
if s.startswith(item, start, end):
return True
return False

return startswith_tuple_impl

if isinstance(substr, types.UnicodeCharSeq):
def startswith_char_seq_impl(s, substr, start=None, end=None):
return s.startswith(str(substr), start, end)

return startswith_char_seq_impl

if isinstance(substr, types.UnicodeType):
def startswith_unicode_impl(s, substr, start=None, end=None):
length = len(s)
sub_length = len(substr)
if start is None:
start = 0
if end is None:
end = length

start, end = _adjust_indices(length, start, end)
if end - start < sub_length:
return False

if sub_length == 0:
return True

s = s[start:end]

return _cmp_region(s, 0, substr, 0, sub_length) == 0

return startswith_unicode_impl

Comment thread
dlee992 marked this conversation as resolved.
Resolved

@overload_method(types.UnicodeType, 'endswith')
def unicode_endswith(s, substr, start=None, end=None):
if not (start is None or isinstance(start, (types.Omitted,
Expand Down
102 changes: 89 additions & 13 deletions numba/tests/test_unicode.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,14 @@ def expandtabs_with_tabsize_kwarg_usecase(s, tabsize):
return s.expandtabs(tabsize=tabsize)


def startswith_with_start_only_usecase(x, y, start):
return x.startswith(y, start)


def startswith_with_start_end_usecase(x, y, start, end):
return x.startswith(y, start, end)


def endswith_with_start_only_usecase(x, y, start):
return x.endswith(y, start)

Expand Down Expand Up @@ -551,23 +559,91 @@ def test_expandtabs_exception_noninteger_tabsize(self):
msg = '"tabsize" must be {}, not float'.format(accepted_types)
self.assertIn(msg, str(raises.exception))

def test_startswith(self, flags=no_pyobj_flags):
def test_startswith_default(self):
pyfunc = startswith_usecase
cfunc = njit(pyfunc)
for a in UNICODE_EXAMPLES:
for b in ['', 'x', a[:-2], a[3:], a, a + a]:
self.assertEqual(pyfunc(a, b),
cfunc(a, b),
'%s, %s' % (a, b))

def test_endswith(self, flags=no_pyobj_flags):
pyfunc = endswith_usecase
cpython_str = ['hello', 'helloworld', '']
cpython_subs = [
'he', 'hello', 'helloworld', 'ello',
'', 'lowo', 'lo', 'he', 'lo', 'o',
]
extra_subs = ['hellohellohello', ' ']
for s in cpython_str + UNICODE_EXAMPLES:
default_subs = ['', 'x', s[:-2], s[3:], s, s + s]
for sub_str in cpython_subs + default_subs + extra_subs:
self.assertEqual(pyfunc(s, sub_str), cfunc(s, sub_str))

def test_startswith_with_start(self):
pyfunc = startswith_with_start_only_usecase
cfunc = njit(pyfunc)
for a in UNICODE_EXAMPLES:
for b in ['', 'x', a[:-2], a[3:], a, a + a]:
self.assertEqual(pyfunc(a, b),
cfunc(a, b),
'%s, %s' % (a, b))

cpython_str = ['hello', 'helloworld', '']
cpython_subs = [
'he', 'hello', 'helloworld', 'ello',
'', 'lowo', 'lo', 'he', 'lo', 'o',
]
extra_subs = ['hellohellohello', ' ']
for s in cpython_str + UNICODE_EXAMPLES:
default_subs = ['', 'x', s[:-2], s[3:], s, s + s]
for sub_str in cpython_subs + default_subs + extra_subs:
for start in list(range(-20, 20)) + [None]:
self.assertEqual(pyfunc(s, sub_str, start),
cfunc(s, sub_str, start))

def test_startswith_with_start_end(self):
pyfunc = startswith_with_start_end_usecase
cfunc = njit(pyfunc)

cpython_str = ['hello', 'helloworld', '']
cpython_subs = [
'he', 'hello', 'helloworld', 'ello',
'', 'lowo', 'lo', 'he', 'lo', 'o',
]
extra_subs = ['hellohellohello', ' ']
for s in cpython_str + UNICODE_EXAMPLES:
default_subs = ['', 'x', s[:-2], s[3:], s, s + s]
for sub_str in cpython_subs + default_subs + extra_subs:
for start in list(range(-20, 20)) + [None]:
for end in list(range(-20, 20)) + [None]:
self.assertEqual(pyfunc(s, sub_str, start, end),
cfunc(s, sub_str, start, end))

def test_startswith_tuple(self):
pyfunc = startswith_usecase
cfunc = njit(pyfunc)

cpython_str = ['hello', 'helloworld', '']
cpython_subs = [
'he', 'hello', 'helloworld', 'ello',
'', 'lowo', 'lo', 'he', 'lo', 'o',
]
extra_subs = ['hellohellohello', ' ']
for s in cpython_str + UNICODE_EXAMPLES:
default_subs = ['', 'x', s[:-2], s[3:], s, s + s]
for sub_str in cpython_subs + default_subs + extra_subs:
tuple_subs = (sub_str, 'lo')
self.assertEqual(pyfunc(s, tuple_subs),
cfunc(s, tuple_subs))

def test_startswith_tuple_args(self):
pyfunc = startswith_with_start_end_usecase
cfunc = njit(pyfunc)

cpython_str = ['hello', 'helloworld', '']
cpython_subs = [
'he', 'hello', 'helloworld', 'ello',
'', 'lowo', 'lo', 'he', 'lo', 'o',
]
extra_subs = ['hellohellohello', ' ']
for s in cpython_str + UNICODE_EXAMPLES:
default_subs = ['', 'x', s[:-2], s[3:], s, s + s]
for sub_str in cpython_subs + default_subs + extra_subs:
for start in list(range(-20, 20)) + [None]:
for end in list(range(-20, 20)) + [None]:
tuple_subs = (sub_str, 'lo')
self.assertEqual(pyfunc(s, tuple_subs, start, end),
cfunc(s, tuple_subs, start, end))

def test_endswith_default(self):
pyfunc = endswith_usecase
Expand Down