RFE #1491485: str/unicode.endswith()/startswith() now accept a tuple as first argument.

This commit is contained in:
Georg Brandl 2006-06-09 18:45:48 +00:00
parent 932f5afbe8
commit 242508160e
5 changed files with 182 additions and 83 deletions

View File

@ -618,8 +618,11 @@ For a list of possible encodings, see section~\ref{standard-encodings}.
\begin{methoddesc}[string]{endswith}{suffix\optional{, start\optional{, end}}}
Return \code{True} if the string ends with the specified \var{suffix},
otherwise return \code{False}. With optional \var{start}, test beginning at
otherwise return \code{False}. \var{suffix} can also be a tuple of
suffixes to look for. With optional \var{start}, test beginning at
that position. With optional \var{end}, stop comparing at that position.
\versionchanged[Accept tuples as \var{suffix}]{2.5}
\end{methoddesc}
\begin{methoddesc}[string]{expandtabs}{\optional{tabsize}}
@ -829,9 +832,12 @@ boundaries. Line breaks are not included in the resulting list unless
\begin{methoddesc}[string]{startswith}{prefix\optional{,
start\optional{, end}}}
Return \code{True} if string starts with the \var{prefix}, otherwise
return \code{False}. With optional \var{start}, test string beginning at
return \code{False}. \var{prefix} can also be a tuple of
suffixes to look for. With optional \var{start}, test string beginning at
that position. With optional \var{end}, stop comparing string at that
position.
\versionchanged[Accept tuples as \var{prefix}]{2.5}
\end{methoddesc}
\begin{methoddesc}[string]{strip}{\optional{chars}}

View File

@ -819,6 +819,21 @@ class MixinStrUnicodeUserStringTest:
self.checkraises(TypeError, 'hello', 'startswith')
self.checkraises(TypeError, 'hello', 'startswith', 42)
# test tuple arguments
self.checkequal(True, 'hello', 'startswith', ('he', 'ha'))
self.checkequal(False, 'hello', 'startswith', ('lo', 'llo'))
self.checkequal(True, 'hello', 'startswith', ('hellox', 'hello'))
self.checkequal(False, 'hello', 'startswith', ())
self.checkequal(True, 'helloworld', 'startswith', ('hellowo',
'rld', 'lowo'), 3)
self.checkequal(False, 'helloworld', 'startswith', ('hellowo', 'ello',
'rld'), 3)
self.checkequal(True, 'hello', 'startswith', ('lo', 'he'), 0, -1)
self.checkequal(False, 'hello', 'startswith', ('he', 'hel'), 0, 1)
self.checkequal(True, 'hello', 'startswith', ('he', 'hel'), 0, 2)
self.checkraises(TypeError, 'hello', 'startswith', (42,))
def test_endswith(self):
self.checkequal(True, 'hello', 'endswith', 'lo')
self.checkequal(False, 'hello', 'endswith', 'he')
@ -853,6 +868,21 @@ class MixinStrUnicodeUserStringTest:
self.checkraises(TypeError, 'hello', 'endswith')
self.checkraises(TypeError, 'hello', 'endswith', 42)
# test tuple arguments
self.checkequal(False, 'hello', 'endswith', ('he', 'ha'))
self.checkequal(True, 'hello', 'endswith', ('lo', 'llo'))
self.checkequal(True, 'hello', 'endswith', ('hellox', 'hello'))
self.checkequal(False, 'hello', 'endswith', ())
self.checkequal(True, 'helloworld', 'endswith', ('hellowo',
'rld', 'lowo'), 3)
self.checkequal(False, 'helloworld', 'endswith', ('hellowo', 'ello',
'rld'), 3, -1)
self.checkequal(True, 'hello', 'endswith', ('hell', 'ell'), 0, -1)
self.checkequal(False, 'hello', 'endswith', ('he', 'hel'), 0, 1)
self.checkequal(True, 'hello', 'endswith', ('he', 'hell'), 0, 4)
self.checkraises(TypeError, 'hello', 'endswith', (42,))
def test___contains__(self):
self.checkequal(True, '', '__contains__', '') # vereq('' in '', True)
self.checkequal(True, 'abc', '__contains__', '') # vereq('' in 'abc', True)
@ -872,7 +902,7 @@ class MixinStrUnicodeUserStringTest:
self.checkequal(u'abc', 'abc', '__getitem__', slice(0, 1000))
self.checkequal(u'a', 'abc', '__getitem__', slice(0, 1))
self.checkequal(u'', 'abc', '__getitem__', slice(0, 0))
# FIXME What about negative indizes? This is handled differently by [] and __getitem__(slice)
# FIXME What about negative indices? This is handled differently by [] and __getitem__(slice)
self.checkraises(TypeError, 'abc', '__getitem__', 'def')

View File

@ -12,6 +12,9 @@ What's New in Python 2.5 beta 1?
Core and builtins
-----------------
- The string and unicode methods startswith() and endswith() now accept
a tuple of prefixes/suffixes to look for. Implements RFE #1491485.
- Buffer objects, at the C level, never used the char buffer
implementation even when the char buffer for the wrapped object was
explicitly requested (originally returned the read or write buffer).

View File

@ -3099,54 +3099,96 @@ string_replace(PyStringObject *self, PyObject *args)
/** End DALKE **/
/* Matches the end (direction > 0) or start (direction < 0) of self
* against substr, using the start and end arguments. Returns
* -1 on error, 0 if not found and 1 if found.
*/
Py_LOCAL(int)
_string_tailmatch(PyStringObject *self, PyObject *substr, Py_ssize_t start,
Py_ssize_t end, int direction)
{
Py_ssize_t len = PyString_GET_SIZE(self);
Py_ssize_t slen;
const char* sub;
const char* str;
if (PyString_Check(substr)) {
sub = PyString_AS_STRING(substr);
slen = PyString_GET_SIZE(substr);
}
#ifdef Py_USING_UNICODE
else if (PyUnicode_Check(substr))
return PyUnicode_Tailmatch((PyObject *)self,
substr, start, end, direction);
#endif
else if (PyObject_AsCharBuffer(substr, &sub, &slen))
return -1;
str = PyString_AS_STRING(self);
string_adjust_indices(&start, &end, len);
if (direction < 0) {
/* startswith */
if (start+slen > len)
return 0;
if (end-start >= slen)
return ! memcmp(str+start, sub, slen);
else
return 0;
} else {
/* endswith */
if (end-start < slen || start > len)
return 0;
if (end-slen > start)
start = end - slen;
if (end-start >= slen)
return ! memcmp(str+start, sub, slen);
else
return 0;
}
}
PyDoc_STRVAR(startswith__doc__,
"S.startswith(prefix[, start[, end]]) -> bool\n\
\n\
Return True if S starts with the specified prefix, False otherwise.\n\
With optional start, test S beginning at that position.\n\
With optional end, stop comparing S at that position.");
With optional end, stop comparing S at that position.\n\
prefix can also be a tuple of strings to try.");
static PyObject *
string_startswith(PyStringObject *self, PyObject *args)
{
const char* str = PyString_AS_STRING(self);
Py_ssize_t len = PyString_GET_SIZE(self);
const char* prefix;
Py_ssize_t plen;
Py_ssize_t start = 0;
Py_ssize_t end = PY_SSIZE_T_MAX;
PyObject *subobj;
int result;
if (!PyArg_ParseTuple(args, "O|O&O&:startswith", &subobj,
_PyEval_SliceIndex, &start, _PyEval_SliceIndex, &end))
return NULL;
if (PyString_Check(subobj)) {
prefix = PyString_AS_STRING(subobj);
plen = PyString_GET_SIZE(subobj);
if (PyTuple_Check(subobj)) {
Py_ssize_t i;
for (i = 0; i < PyTuple_GET_SIZE(subobj); i++) {
result = _string_tailmatch(self,
PyTuple_GET_ITEM(subobj, i),
start, end, -1);
if (result == -1)
return NULL;
else if (result) {
Py_RETURN_TRUE;
}
}
Py_RETURN_FALSE;
}
#ifdef Py_USING_UNICODE
else if (PyUnicode_Check(subobj)) {
Py_ssize_t rc;
rc = PyUnicode_Tailmatch((PyObject *)self,
subobj, start, end, -1);
if (rc == -1)
return NULL;
else
return PyBool_FromLong((long) rc);
}
#endif
else if (PyObject_AsCharBuffer(subobj, &prefix, &plen))
result = _string_tailmatch(self, subobj, start, end, -1);
if (result == -1)
return NULL;
string_adjust_indices(&start, &end, len);
if (start+plen > len)
return PyBool_FromLong(0);
if (end-start >= plen)
return PyBool_FromLong(!memcmp(str+start, prefix, plen));
else
return PyBool_FromLong(0);
return PyBool_FromLong(result);
}
@ -3155,51 +3197,39 @@ PyDoc_STRVAR(endswith__doc__,
\n\
Return True if S ends with the specified suffix, False otherwise.\n\
With optional start, test S beginning at that position.\n\
With optional end, stop comparing S at that position.");
With optional end, stop comparing S at that position.\n\
suffix can also be a tuple of strings to try.");
static PyObject *
string_endswith(PyStringObject *self, PyObject *args)
{
const char* str = PyString_AS_STRING(self);
Py_ssize_t len = PyString_GET_SIZE(self);
const char* suffix;
Py_ssize_t slen;
Py_ssize_t start = 0;
Py_ssize_t end = PY_SSIZE_T_MAX;
PyObject *subobj;
int result;
if (!PyArg_ParseTuple(args, "O|O&O&:endswith", &subobj,
_PyEval_SliceIndex, &start, _PyEval_SliceIndex, &end))
return NULL;
if (PyString_Check(subobj)) {
suffix = PyString_AS_STRING(subobj);
slen = PyString_GET_SIZE(subobj);
if (PyTuple_Check(subobj)) {
Py_ssize_t i;
for (i = 0; i < PyTuple_GET_SIZE(subobj); i++) {
result = _string_tailmatch(self,
PyTuple_GET_ITEM(subobj, i),
start, end, +1);
if (result == -1)
return NULL;
else if (result) {
Py_RETURN_TRUE;
}
}
Py_RETURN_FALSE;
}
#ifdef Py_USING_UNICODE
else if (PyUnicode_Check(subobj)) {
Py_ssize_t rc;
rc = PyUnicode_Tailmatch((PyObject *)self,
subobj, start, end, +1);
if (rc == -1)
return NULL;
else
return PyBool_FromLong((long) rc);
}
#endif
else if (PyObject_AsCharBuffer(subobj, &suffix, &slen))
result = _string_tailmatch(self, subobj, start, end, +1);
if (result == -1)
return NULL;
string_adjust_indices(&start, &end, len);
if (end-start < slen || start > len)
return PyBool_FromLong(0);
if (end-slen > start)
start = end - slen;
if (end-start >= slen)
return PyBool_FromLong(!memcmp(str+start, suffix, slen));
else
return PyBool_FromLong(0);
return PyBool_FromLong(result);
}

View File

@ -6667,29 +6667,44 @@ PyDoc_STRVAR(startswith__doc__,
\n\
Return True if S starts with the specified prefix, False otherwise.\n\
With optional start, test S beginning at that position.\n\
With optional end, stop comparing S at that position.");
With optional end, stop comparing S at that position.\n\
prefix can also be a tuple of strings to try.");
static PyObject *
unicode_startswith(PyUnicodeObject *self,
PyObject *args)
{
PyObject *subobj;
PyUnicodeObject *substring;
Py_ssize_t start = 0;
Py_ssize_t end = PY_SSIZE_T_MAX;
PyObject *result;
int result;
if (!PyArg_ParseTuple(args, "O|O&O&:startswith", &substring,
if (!PyArg_ParseTuple(args, "O|O&O&:startswith", &subobj,
_PyEval_SliceIndex, &start, _PyEval_SliceIndex, &end))
return NULL;
substring = (PyUnicodeObject *)PyUnicode_FromObject(
(PyObject *)substring);
if (PyTuple_Check(subobj)) {
Py_ssize_t i;
for (i = 0; i < PyTuple_GET_SIZE(subobj); i++) {
substring = (PyUnicodeObject *)PyUnicode_FromObject(
PyTuple_GET_ITEM(subobj, i));
if (substring == NULL)
return NULL;
result = tailmatch(self, substring, start, end, -1);
Py_DECREF(substring);
if (result) {
Py_RETURN_TRUE;
}
}
/* nothing matched */
Py_RETURN_FALSE;
}
substring = (PyUnicodeObject *)PyUnicode_FromObject(subobj);
if (substring == NULL)
return NULL;
result = PyBool_FromLong(tailmatch(self, substring, start, end, -1));
return NULL;
result = tailmatch(self, substring, start, end, -1);
Py_DECREF(substring);
return result;
return PyBool_FromLong(result);
}
@ -6698,29 +6713,44 @@ PyDoc_STRVAR(endswith__doc__,
\n\
Return True if S ends with the specified suffix, False otherwise.\n\
With optional start, test S beginning at that position.\n\
With optional end, stop comparing S at that position.");
With optional end, stop comparing S at that position.\n\
suffix can also be a tuple of strings to try.");
static PyObject *
unicode_endswith(PyUnicodeObject *self,
PyObject *args)
{
PyObject *subobj;
PyUnicodeObject *substring;
Py_ssize_t start = 0;
Py_ssize_t end = PY_SSIZE_T_MAX;
PyObject *result;
int result;
if (!PyArg_ParseTuple(args, "O|O&O&:endswith", &substring,
_PyEval_SliceIndex, &start, _PyEval_SliceIndex, &end))
if (!PyArg_ParseTuple(args, "O|O&O&:endswith", &subobj,
_PyEval_SliceIndex, &start, _PyEval_SliceIndex, &end))
return NULL;
substring = (PyUnicodeObject *)PyUnicode_FromObject(
(PyObject *)substring);
if (PyTuple_Check(subobj)) {
Py_ssize_t i;
for (i = 0; i < PyTuple_GET_SIZE(subobj); i++) {
substring = (PyUnicodeObject *)PyUnicode_FromObject(
PyTuple_GET_ITEM(subobj, i));
if (substring == NULL)
return NULL;
result = tailmatch(self, substring, start, end, +1);
Py_DECREF(substring);
if (result) {
Py_RETURN_TRUE;
}
}
Py_RETURN_FALSE;
}
substring = (PyUnicodeObject *)PyUnicode_FromObject(subobj);
if (substring == NULL)
return NULL;
result = PyBool_FromLong(tailmatch(self, substring, start, end, +1));
return NULL;
result = tailmatch(self, substring, start, end, +1);
Py_DECREF(substring);
return result;
return PyBool_FromLong(result);
}