83

I have a bunch of strings, some of them have ' rec'. I want to remove that only if those are the last 4 characters.

So in other words I have

somestring = 'this is some string rec'

and I want it to become

somestring = 'this is some string'

What is the Python way to approach this?

Boris Verkhovskiy
  • 14,854
  • 11
  • 100
  • 103
Alex Gordon
  • 57,446
  • 287
  • 670
  • 1,062

11 Answers11

104
def rchop(s, suffix):
    if suffix and s.endswith(suffix):
        return s[:-len(suffix)]
    return s

somestring = 'this is some string rec'
rchop(somestring, ' rec')  # returns 'this is some string'
Boris Verkhovskiy
  • 14,854
  • 11
  • 100
  • 103
Jack Kelly
  • 18,264
  • 2
  • 56
  • 81
  • 4
    Note that [`endswith`](https://docs.python.org/library/stdtypes.html#str.endswith) can also take a tuple of suffixes to look for. If someone passes a tuple as the `suffix` with this function, you'll get the wrong result. It will check a list of strings but remove the length of the list of strings, not the length of the matching string. – Boris Verkhovskiy Mar 24 '20 at 09:33
30

Since you have to get len(trailing) anyway (where trailing is the string you want to remove IF it's trailing), I'd recommend avoiding the slight duplication of work that .endswith would cause in this case. Of course, the proof of the code is in the timing, so, let's do some measurement (naming the functions after the respondents proposing them):

import re

astring = 'this is some string rec'
trailing = ' rec'

def andrew(astring=astring, trailing=trailing):
    regex = r'(.*)%s$' % re.escape(trailing)
    return re.sub(regex, r'\1', astring)

def jack0(astring=astring, trailing=trailing):
    if astring.endswith(trailing):
        return astring[:-len(trailing)]
    return astring

def jack1(astring=astring, trailing=trailing):
    regex = r'%s$' % re.escape(trailing)
    return re.sub(regex, '', astring)

def alex(astring=astring, trailing=trailing):
    thelen = len(trailing)
    if astring[-thelen:] == trailing:
        return astring[:-thelen]
    return astring

Say we've named this python file a.py and it's in the current directory; now, ...:

$ python2.6 -mtimeit -s'import a' 'a.andrew()'
100000 loops, best of 3: 19 usec per loop
$ python2.6 -mtimeit -s'import a' 'a.jack0()'
1000000 loops, best of 3: 0.564 usec per loop
$ python2.6 -mtimeit -s'import a' 'a.jack1()'
100000 loops, best of 3: 9.83 usec per loop
$ python2.6 -mtimeit -s'import a' 'a.alex()'
1000000 loops, best of 3: 0.479 usec per loop

As you see, the RE-based solutions are "hopelessly outclassed" (as often happens when one "overkills" a problem -- possibly one of the reasons REs have such a bad rep in the Python community!-), though the suggestion in @Jack's comment is way better than @Andrew's original. The string-based solutions, as expected, shing, with my endswith-avoiding one having a miniscule advantage over @Jack's (being just 15% faster). So, both pure-string ideas are good (as well as both being concise and clear) -- I prefer my variant a little bit only because I am, by character, a frugal (some might say, stingy;-) person... "waste not, want not"!-)

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • what do you have a space in the import a' 'a.xxx ? – Blankman Sep 08 '10 at 15:49
  • @Blankman, it's a bash command running Python: the setup (`-s`) is one argument, the code being timed the other. Each is quoted so I don't have to worry about it including spaces and/or special character, os course. You always separate arguments with spaces in bash (and most other shells, including Windows' own cmd.exe, so I'm pretty surprised at your question!), and quoting arguments to a shell command to preserve spaces and special characters within each argument is also definitely not what I would call a peculiar, rare, or advanced usage of any shell...!-) – Alex Martelli Sep 08 '10 at 17:30
  • Oh I see you've bypassed `endswith` as I mentioned in Jack's answer. Caching the len also avoids Python's (and C's!) terrible call overhead. – Matt Joiner Sep 12 '10 at 08:12
  • 1
    I'm wondering what would be the performances if the regex where compiled only once and reused multiple times. – Conchylicultor Nov 30 '17 at 22:00
30

Starting in Python 3.9, you can use removesuffix:

'this is some string rec'.removesuffix(' rec')
# 'this is some string'
Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190
27

If speed is not important, use regex:

import re

somestring='this is some string rec'

somestring = re.sub(' rec$', '', somestring)
10

Here is a one-liner version of Jack Kelly's answer along with its sibling:

def rchop(s, sub):
    return s[:-len(sub)] if sub && s.endswith(sub) else s

def lchop(s, sub):
    return s[len(sub):] if s.startswith(sub) else s
artless noise
  • 21,212
  • 6
  • 68
  • 105
cdiggins
  • 17,602
  • 7
  • 105
  • 102
  • See: https://peps.python.org/pep-0616/#specification for similar code. So, this and Jack Kelly's answer would be better to use `removesuffix()` and `removeprefix()` to be compatible. Those versions API differ in an 'object' call as oppose to passing the string. – artless noise Jun 06 '23 at 14:24
4

You could use a regular expression as well:

from re import sub

str = r"this is some string rec"
regex = r"(.*)\srec$"
print sub(regex, r"\1", str)
Andrew Hare
  • 344,730
  • 71
  • 640
  • 635
0

As kind of one liner generator joined:

test = """somestring='this is some string rec'
this is some string in the end word rec
This has not the word."""
match = 'rec'
print('\n'.join((line[:-len(match)] if line.endswith(match) else line)
      for line in test.splitlines()))
""" Output:
somestring='this is some string rec'
this is some string in the end word 
This has not the word.
"""
Tony Veijalainen
  • 5,447
  • 23
  • 31
0

Using more_itertools, we can rstrip strings that pass a predicate.

Installation

> pip install more_itertools

Code

import more_itertools as mit


iterable = "this is some string rec".split()
" ".join(mit.rstrip(iterable, pred=lambda x: x in {"rec", " "}))
# 'this is some string'

" ".join(mit.rstrip(iterable, pred=lambda x: x in {"rec", " "}))
# 'this is some string'

Here we pass all trailing items we wish to strip from the end.

See also the more_itertools docs for details.

pylang
  • 40,867
  • 14
  • 129
  • 121
0

Taking inspiration from @David Foster's answer, I would do

def _remove_suffix(text, suffix):
    if text is not None and suffix is not None:
        return text[:-len(suffix)] if text.endswith(suffix) else text
    else:
        return text

Reference: Python string slicing

y2k-shubham
  • 10,183
  • 11
  • 55
  • 131
0

def remove_trailing_string(content, trailing):
    """
    Strip trailing component `trailing` from `content` if it exists.
    """
    if content.endswith(trailing) and content != trailing:
        return content[:-len(trailing)]
    return content
Ehsan Ahmadi
  • 1,382
  • 15
  • 16
-2

use:

somestring.rsplit(' rec')[0]
  • 2
    This does not always work. It will split at the string at all occurrences of ` rec` and return the first fragment. The prefix l and r only make a difference, if the separator string can overlap or a maximum number of splits is specified. – jan-glx Oct 16 '18 at 18:27