Skip to content

Commit 444156e

Browse files
gh-117431: Adapt str.startswith and str.endswith to Argument Clinic (#117466)
This change gives a significant speedup, as the METH_FASTCALL calling convention is now used.
1 parent 65524ab commit 444156e

File tree

4 files changed

+149
-49
lines changed

4 files changed

+149
-49
lines changed

Lib/test/string_tests.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1513,9 +1513,9 @@ def test_find_etc_raise_correct_error_messages(self):
15131513
x, None, None, None)
15141514
self.assertRaisesRegex(TypeError, r'^count\(', s.count,
15151515
x, None, None, None)
1516-
self.assertRaisesRegex(TypeError, r'^startswith\(', s.startswith,
1516+
self.assertRaisesRegex(TypeError, r'^startswith\b', s.startswith,
15171517
x, None, None, None)
1518-
self.assertRaisesRegex(TypeError, r'^endswith\(', s.endswith,
1518+
self.assertRaisesRegex(TypeError, r'^endswith\b', s.endswith,
15191519
x, None, None, None)
15201520

15211521
# issue #15534
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Improve the performance of :meth:`str.startswith` and :meth:`str.endswith`
2+
by adapting them to the :c:macro:`METH_FASTCALL` calling convention.

Objects/clinic/unicodeobject.c.h

Lines changed: 103 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Objects/unicodeobject.c

Lines changed: 42 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -13021,40 +13021,41 @@ unicode_zfill_impl(PyObject *self, Py_ssize_t width)
1302113021
return u;
1302213022
}
1302313023

13024-
PyDoc_STRVAR(startswith__doc__,
13025-
"S.startswith(prefix[, start[, end]]) -> bool\n\
13026-
\n\
13027-
Return True if S starts with the specified prefix, False otherwise.\n\
13028-
With optional start, test S beginning at that position.\n\
13029-
With optional end, stop comparing S at that position.\n\
13030-
prefix can also be a tuple of strings to try.");
13024+
/*[clinic input]
13025+
@text_signature "($self, prefix[, start[, end]], /)"
13026+
str.startswith as unicode_startswith
13027+
13028+
prefix as subobj: object
13029+
A string or a tuple of strings to try.
13030+
start: slice_index(accept={int, NoneType}, c_default='0') = None
13031+
Optional start position. Default: start of the string.
13032+
end: slice_index(accept={int, NoneType}, c_default='PY_SSIZE_T_MAX') = None
13033+
Optional stop position. Default: end of the string.
13034+
/
13035+
13036+
Return True if the string starts with the specified prefix, False otherwise.
13037+
[clinic start generated code]*/
1303113038

1303213039
static PyObject *
13033-
unicode_startswith(PyObject *self,
13034-
PyObject *args)
13040+
unicode_startswith_impl(PyObject *self, PyObject *subobj, Py_ssize_t start,
13041+
Py_ssize_t end)
13042+
/*[clinic end generated code: output=4bd7cfd0803051d4 input=5f918b5f5f89d856]*/
1303513043
{
13036-
PyObject *subobj;
13037-
PyObject *substring;
13038-
Py_ssize_t start = 0;
13039-
Py_ssize_t end = PY_SSIZE_T_MAX;
13040-
int result;
13041-
13042-
if (!asciilib_parse_args_finds("startswith", args, &subobj, &start, &end))
13043-
return NULL;
1304413044
if (PyTuple_Check(subobj)) {
1304513045
Py_ssize_t i;
1304613046
for (i = 0; i < PyTuple_GET_SIZE(subobj); i++) {
13047-
substring = PyTuple_GET_ITEM(subobj, i);
13047+
PyObject *substring = PyTuple_GET_ITEM(subobj, i);
1304813048
if (!PyUnicode_Check(substring)) {
1304913049
PyErr_Format(PyExc_TypeError,
1305013050
"tuple for startswith must only contain str, "
1305113051
"not %.100s",
1305213052
Py_TYPE(substring)->tp_name);
1305313053
return NULL;
1305413054
}
13055-
result = tailmatch(self, substring, start, end, -1);
13056-
if (result == -1)
13055+
int result = tailmatch(self, substring, start, end, -1);
13056+
if (result < 0) {
1305713057
return NULL;
13058+
}
1305813059
if (result) {
1305913060
Py_RETURN_TRUE;
1306013061
}
@@ -13068,47 +13069,41 @@ unicode_startswith(PyObject *self,
1306813069
"a tuple of str, not %.100s", Py_TYPE(subobj)->tp_name);
1306913070
return NULL;
1307013071
}
13071-
result = tailmatch(self, subobj, start, end, -1);
13072-
if (result == -1)
13072+
int result = tailmatch(self, subobj, start, end, -1);
13073+
if (result < 0) {
1307313074
return NULL;
13075+
}
1307413076
return PyBool_FromLong(result);
1307513077
}
1307613078

1307713079

13078-
PyDoc_STRVAR(endswith__doc__,
13079-
"S.endswith(suffix[, start[, end]]) -> bool\n\
13080-
\n\
13081-
Return True if S ends with the specified suffix, False otherwise.\n\
13082-
With optional start, test S beginning at that position.\n\
13083-
With optional end, stop comparing S at that position.\n\
13084-
suffix can also be a tuple of strings to try.");
13080+
/*[clinic input]
13081+
@text_signature "($self, prefix[, start[, end]], /)"
13082+
str.endswith as unicode_endswith = str.startswith
13083+
13084+
Return True if the string ends with the specified prefix, False otherwise.
13085+
[clinic start generated code]*/
1308513086

1308613087
static PyObject *
13087-
unicode_endswith(PyObject *self,
13088-
PyObject *args)
13088+
unicode_endswith_impl(PyObject *self, PyObject *subobj, Py_ssize_t start,
13089+
Py_ssize_t end)
13090+
/*[clinic end generated code: output=cce6f8ceb0102ca9 input=82cd5ce9e7623646]*/
1308913091
{
13090-
PyObject *subobj;
13091-
PyObject *substring;
13092-
Py_ssize_t start = 0;
13093-
Py_ssize_t end = PY_SSIZE_T_MAX;
13094-
int result;
13095-
13096-
if (!asciilib_parse_args_finds("endswith", args, &subobj, &start, &end))
13097-
return NULL;
1309813092
if (PyTuple_Check(subobj)) {
1309913093
Py_ssize_t i;
1310013094
for (i = 0; i < PyTuple_GET_SIZE(subobj); i++) {
13101-
substring = PyTuple_GET_ITEM(subobj, i);
13095+
PyObject *substring = PyTuple_GET_ITEM(subobj, i);
1310213096
if (!PyUnicode_Check(substring)) {
1310313097
PyErr_Format(PyExc_TypeError,
1310413098
"tuple for endswith must only contain str, "
1310513099
"not %.100s",
1310613100
Py_TYPE(substring)->tp_name);
1310713101
return NULL;
1310813102
}
13109-
result = tailmatch(self, substring, start, end, +1);
13110-
if (result == -1)
13103+
int result = tailmatch(self, substring, start, end, +1);
13104+
if (result < 0) {
1311113105
return NULL;
13106+
}
1311213107
if (result) {
1311313108
Py_RETURN_TRUE;
1311413109
}
@@ -13121,9 +13116,10 @@ unicode_endswith(PyObject *self,
1312113116
"a tuple of str, not %.100s", Py_TYPE(subobj)->tp_name);
1312213117
return NULL;
1312313118
}
13124-
result = tailmatch(self, subobj, start, end, +1);
13125-
if (result == -1)
13119+
int result = tailmatch(self, subobj, start, end, +1);
13120+
if (result < 0) {
1312613121
return NULL;
13122+
}
1312713123
return PyBool_FromLong(result);
1312813124
}
1312913125

@@ -13576,8 +13572,8 @@ static PyMethodDef unicode_methods[] = {
1357613572
UNICODE_SWAPCASE_METHODDEF
1357713573
UNICODE_TRANSLATE_METHODDEF
1357813574
UNICODE_UPPER_METHODDEF
13579-
{"startswith", (PyCFunction) unicode_startswith, METH_VARARGS, startswith__doc__},
13580-
{"endswith", (PyCFunction) unicode_endswith, METH_VARARGS, endswith__doc__},
13575+
UNICODE_STARTSWITH_METHODDEF
13576+
UNICODE_ENDSWITH_METHODDEF
1358113577
UNICODE_REMOVEPREFIX_METHODDEF
1358213578
UNICODE_REMOVESUFFIX_METHODDEF
1358313579
UNICODE_ISASCII_METHODDEF

0 commit comments

Comments
 (0)