Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-39939: Add str.removeprefix and str.removesuffix #18939

Merged
merged 39 commits into from
Apr 22, 2020
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
0addc43
Add cutprefix and cutsuffix methods to str, bytes, and bytearray.
sweeneyde Mar 10, 2020
3adb9fa
pep 7: lining up argumenets
sweeneyde Mar 11, 2020
a7a1bc8
Revert "pep 7: lining up argumenets"
sweeneyde Mar 11, 2020
fe18644
pep 7: line up arguemnts
sweeneyde Mar 11, 2020
ff8e3c6
pep 7: line up arguemnts
sweeneyde Mar 11, 2020
5339a46
📜🤖 Added by blurb_it.
blurb-it[bot] Mar 11, 2020
cc85978
add UserString methods
sweeneyde Mar 11, 2020
1442ffe
Merge branch 'cut_affix' of /~https://github.com/sweeneyde/cpython into…
sweeneyde Mar 11, 2020
111b0f9
update count of objects in test_doctests
sweeneyde Mar 11, 2020
8265e4d
restore clinic output
sweeneyde Mar 11, 2020
7401b87
update count of objects in test_doctests
sweeneyde Mar 11, 2020
e550171
return original when bytes.cut***fix does not find match
sweeneyde Mar 11, 2020
0a5d0a9
Document cutprefix and cutsuffix
sweeneyde Mar 12, 2020
a126438
fix doctest in docs
sweeneyde Mar 12, 2020
fbc4a50
Add credit
sweeneyde Mar 12, 2020
3783dc3
make the empty affix case fast
sweeneyde Mar 12, 2020
428e733
clarified: one affix at a time
sweeneyde Mar 12, 2020
5796757
ensure tuples are not allowed
sweeneyde Mar 12, 2020
6fe9ac5
Fix userstring type behavior
sweeneyde Mar 12, 2020
13e8296
WhatsNew and ACKS
sweeneyde Mar 12, 2020
49fa220
WhatsNew and ACKS
sweeneyde Mar 12, 2020
550beca
fix spelling
sweeneyde Mar 12, 2020
01d0655
Direct readers from (l/r)strip to cut***fix
sweeneyde Mar 12, 2020
3c0e350
Merge branch 'cut_affix' of /~https://github.com/sweeneyde/cpython into…
sweeneyde Mar 12, 2020
fe80ba8
Fix typo in docs
sweeneyde Mar 16, 2020
ae23692
minor c formatting consistency
sweeneyde Mar 16, 2020
a9e253c
copy/paste errors; don't say 'return the original'
sweeneyde Mar 20, 2020
4c33b74
changed 'cut' to 'remove'
sweeneyde Mar 25, 2020
4413e2e
Change method names in whatsnew
sweeneyde Mar 25, 2020
5dfa968
Update Misc/NEWS.d/next/Core and Builtins/2020-03-11-19-17-36.bpo-399…
sweeneyde Mar 25, 2020
aa6eede
new names in the whatsnew header
sweeneyde Mar 28, 2020
d941711
Merge branch 'master' into cut_affix
sweeneyde Apr 9, 2020
f55836d
add examples of differences between l/rstrip and removeaffix
sweeneyde Apr 21, 2020
8d0584a
Merge branch 'cut_affix' of /~https://github.com/sweeneyde/cpython into…
sweeneyde Apr 21, 2020
8b6267a
apply changes from review
sweeneyde Apr 22, 2020
61cd530
apply changes from review
sweeneyde Apr 22, 2020
ffe72f1
more documentation tweaks
sweeneyde Apr 22, 2020
d8f5a99
clean up the NEWS entry
sweeneyde Apr 22, 2020
3df1f38
mention arg type in docstrings
sweeneyde Apr 22, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Lib/collections/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1202,6 +1202,14 @@ def count(self, sub, start=0, end=_sys.maxsize):
if isinstance(sub, UserString):
sub = sub.data
return self.data.count(sub, start, end)
def cutprefix(self, prefix, /):
if self.startswith(prefix):
return self[len(prefix):]
return self[:]
def cutsuffix(self, suffix, /):
if self.endswith(suffix):
return self[:len(self)-len(suffix)]
return self[:]
def encode(self, encoding='utf-8', errors='strict'):
encoding = 'utf-8' if encoding is None else encoding
errors = 'strict' if errors is None else errors
Expand Down
34 changes: 34 additions & 0 deletions Lib/test/string_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,40 @@ def test_replace_overflow(self):
self.checkraises(OverflowError, A2_16, "replace", "A", A2_16)
self.checkraises(OverflowError, A2_16, "replace", "AA", A2_16+A2_16)

def test_cutprefix(self):
self.checkequal('am', 'spam', 'cutprefix', 'sp')
self.checkequal('spamspam', 'spamspamspam', 'cutprefix', 'spam')
self.checkequal('spam', 'spam', 'cutprefix', 'python')
self.checkequal('spam', 'spam', 'cutprefix', 'spider')
self.checkequal('spam', 'spam', 'cutprefix', 'spam and eggs')

self.checkequal('', '', 'cutprefix', '')
self.checkequal('', '', 'cutprefix', 'abcde')
self.checkequal('abcde', 'abcde', 'cutprefix', '')
self.checkequal('', 'abcde', 'cutprefix', 'abcde')

self.checkraises(TypeError, 'hello', 'cutprefix')
self.checkraises(TypeError, 'hello', 'cutprefix', 42)
self.checkraises(TypeError, 'hello', 'cutprefix', 42, 'h')
self.checkraises(TypeError, 'hello', 'cutprefix', 'h', 42)

def test_cutsuffix(self):
self.checkequal('sp', 'spam', 'cutsuffix', 'am')
self.checkequal('spamspam', 'spamspamspam', 'cutsuffix', 'spam')
self.checkequal('spam', 'spam', 'cutsuffix', 'python')
self.checkequal('spam', 'spam', 'cutsuffix', 'blam')
self.checkequal('spam', 'spam', 'cutsuffix', 'eggs and spam')

self.checkequal('', '', 'cutsuffix', '')
self.checkequal('', '', 'cutsuffix', 'abcde')
self.checkequal('abcde', 'abcde', 'cutsuffix', '')
self.checkequal('', 'abcde', 'cutsuffix', 'abcde')

self.checkraises(TypeError, 'hello', 'cutsuffix')
self.checkraises(TypeError, 'hello', 'cutsuffix', 42)
self.checkraises(TypeError, 'hello', 'cutsuffix', 42, 'h')
self.checkraises(TypeError, 'hello', 'cutsuffix', 'h', 42)

def test_capitalize(self):
self.checkequal(' hello ', ' hello ', 'capitalize')
self.checkequal('Hello ', 'Hello ','capitalize')
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_doctest.py
Original file line number Diff line number Diff line change
Expand Up @@ -661,7 +661,7 @@ def non_Python_modules(): r"""

>>> import builtins
>>> tests = doctest.DocTestFinder().find(builtins)
>>> 800 < len(tests) < 820 # approximate number of objects with docstrings
>>> 800 < len(tests) < 826 # approximate number of objects with docstrings
True
>>> real_tests = [t for t in tests if len(t.examples) > 0]
>>> len(real_tests) # objects that actually have doctests
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added str.cutprefix and str.cutsuffix methods and corresponding bytes and bytearray methods to cut affixes off of a string, if present.
65 changes: 65 additions & 0 deletions Objects/bytearrayobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1185,6 +1185,69 @@ bytearray_endswith(PyByteArrayObject *self, PyObject *args)
return _Py_bytes_endswith(PyByteArray_AS_STRING(self), PyByteArray_GET_SIZE(self), args);
}

/*[clinic input]
bytearray.cutprefix as bytearray_cutprefix

prefix: Py_buffer
/

Return a copy of the bytearray with a given prefix removed if present.

If the bytearray starts with the prefix, return b[len(prefix):].
Otherwise, return a copy of the original bytearray.
[clinic start generated code]*/

static PyObject *
bytearray_cutprefix_impl(PyByteArrayObject *self, Py_buffer *prefix)
/*[clinic end generated code: output=21632e315d769b4b input=1848afa62344e091]*/
{
const char *self_start = PyByteArray_AS_STRING(self);
Py_ssize_t self_len = PyByteArray_GET_SIZE(self);
const char *prefix_start = prefix->buf;
Py_ssize_t prefix_len = prefix->len;

if (self_len >= prefix_len
&& memcmp(self_start, prefix_start, prefix_len) == 0)
{
return PyByteArray_FromStringAndSize(self_start + prefix_len,
self_len - prefix_len);
}

return PyByteArray_FromStringAndSize(self_start, self_len);
}

/*[clinic input]
bytearray.cutsuffix as bytearray_cutsuffix

suffix: Py_buffer
/

Return a copy of the bytearray with a given suffix removed if present.

If the bytearray ends with the suffix, return b[:len(b)-len(suffix)].
Otherwise, return a copy of the original bytearray.
[clinic start generated code]*/

static PyObject *
bytearray_cutsuffix_impl(PyByteArrayObject *self, Py_buffer *suffix)
/*[clinic end generated code: output=9862e6f256b4e5a0 input=8f8c10709806b42b]*/
{
const char *self_start = PyByteArray_AS_STRING(self);
Py_ssize_t self_len = PyByteArray_GET_SIZE(self);
const char *suffix_start = suffix->buf;
Py_ssize_t suffix_len = suffix->len;

if (self_len >= suffix_len
&& memcmp(self_start + self_len - suffix_len,
suffix_start, suffix_len) == 0)
{
return PyByteArray_FromStringAndSize(self_start,
self_len - suffix_len);
}

return PyByteArray_FromStringAndSize(self_start, self_len);
}


/*[clinic input]
bytearray.translate
Expand Down Expand Up @@ -2207,6 +2270,8 @@ bytearray_methods[] = {
BYTEARRAY_POP_METHODDEF
BYTEARRAY_REMOVE_METHODDEF
BYTEARRAY_REPLACE_METHODDEF
BYTEARRAY_CUTPREFIX_METHODDEF
BYTEARRAY_CUTSUFFIX_METHODDEF
BYTEARRAY_REVERSE_METHODDEF
{"rfind", (PyCFunction)bytearray_rfind, METH_VARARGS, _Py_rfind__doc__},
{"rindex", (PyCFunction)bytearray_rindex, METH_VARARGS, _Py_rindex__doc__},
Expand Down
64 changes: 64 additions & 0 deletions Objects/bytesobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -2181,6 +2181,68 @@ bytes_replace_impl(PyBytesObject *self, Py_buffer *old, Py_buffer *new,

/** End DALKE **/

/*[clinic input]
bytes.cutprefix as bytes_cutprefix

prefix: Py_buffer
/

Remove a specified prefix, if present.

If the bytes starts with the prefix, return b[len(prefix):].
Otherwise, return the original bytes.
[clinic start generated code]*/

static PyObject *
bytes_cutprefix_impl(PyBytesObject *self, Py_buffer *prefix)
/*[clinic end generated code: output=5e5ef0cf576a8a9c input=f5409d4eb86164e8]*/
{
const char *self_start = PyBytes_AS_STRING(self);
Py_ssize_t self_len = PyBytes_GET_SIZE(self);
const char *prefix_start = prefix->buf;
Py_ssize_t prefix_len = prefix->len;

if (self_len >= prefix_len
&& memcmp(self_start, prefix_start, prefix_len) == 0)
{
return PyBytes_FromStringAndSize(self_start + prefix_len,
self_len - prefix_len);
}

return PyBytes_FromStringAndSize(self_start, self_len);
}

/*[clinic input]
bytes.cutsuffix as bytes_cutsuffix

suffix: Py_buffer
/

Remove a specified suffix, if present.

If the bytes ends with the suffix, return b[:len(b)-len(prefix)].
Otherwise, return the original bytes.
[clinic start generated code]*/

static PyObject *
bytes_cutsuffix_impl(PyBytesObject *self, Py_buffer *suffix)
/*[clinic end generated code: output=303549ce0a999724 input=5e4ee249c40f7bf6]*/
{
const char* self_start = PyBytes_AS_STRING(self);
Py_ssize_t self_len = PyBytes_GET_SIZE(self);
const char* suffix_start = suffix->buf;
Py_ssize_t suffix_len = suffix->len;

if (self_len >= suffix_len
&& memcmp(self_start + self_len - suffix_len,
suffix_start, suffix_len) == 0)
{
return PyBytes_FromStringAndSize(self_start,
self_len - suffix_len);
}

return PyBytes_FromStringAndSize(self_start, self_len);
}

static PyObject *
bytes_startswith(PyBytesObject *self, PyObject *args)
Expand Down Expand Up @@ -2420,6 +2482,8 @@ bytes_methods[] = {
BYTES_MAKETRANS_METHODDEF
BYTES_PARTITION_METHODDEF
BYTES_REPLACE_METHODDEF
BYTES_CUTPREFIX_METHODDEF
BYTES_CUTSUFFIX_METHODDEF
{"rfind", (PyCFunction)bytes_rfind, METH_VARARGS, _Py_rfind__doc__},
{"rindex", (PyCFunction)bytes_rindex, METH_VARARGS, _Py_rindex__doc__},
STRINGLIB_RJUST_METHODDEF
Expand Down
80 changes: 79 additions & 1 deletion Objects/clinic/bytearrayobject.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading