bytes.removesuffix()

The mirror of removeprefix, and the correct tool for file extensions. Matches the exact sequence, once, and is a no-op when absent.

Bytes methodPython 3.9+Live demo
Common call
name.removesuffix(b'.txt')
Returns
a new bytes object, or the original when the suffix is absent
Replaces
data[:-len(s)] if s and data.endswith(s) else data
Watch out
removes ONCE — and the pre-3.9 idiom breaks on an empty suffix
bytes.removesuffix(suffixsuffix — The exact byte sequence to remove from the end. An empty suffix is a harmless no-op.type: bytes · required)
→ bytes

Demo

Live evaluation
Try:
Inputs
sstrdata (encoded as utf-8)
suffixstrsuffix to remove
Output
bytes('file.txt', 'utf-8').removesuffix(bytes('.txt', 'utf-8'))
b'file'

When the data ends with the exact suffix it is removed; otherwise the data is returned unchanged. The third case is the one that justifies the method: text.txt becomes text, where rstrip(b".txt") would have chewed through to te because it strips a set of bytes. The doubled case shows the "once" rule — namexx with suffix x loses one x.

Parameters

NameTypeRequiredDescription
suffixbytesyesThe exact byte sequence to remove from the end. An empty suffix is a harmless no-op.

Return value

bytes — The data with suffix removed from the end if it is there — otherwise the data unchanged. Never raises for a missing suffix.

Common patterns

Strip a file extension
The canonical use, and the one rstrip gets wrong.
stem = filename.removesuffix(b'.txt')
Drop a trailing terminator
Exact match, so a single terminator is removed and a doubled one keeps one.
body = frame.removesuffix(b'\r\n')
Pre-3.9 equivalent
Note the guard against an empty suffix, which the slice idiom needs.
stem = d[:-len(s)] if s and d.endswith(s) else d

Examples

1. Present
b'file.txt'.removesuffix(b'.txt')
Returns
b'file'
2. Absent
b'file.csv'.removesuffix(b'.txt')
Returns
b'file.csv'
3. rstrip breaks
b'text.txt'.rstrip(b'.txt')
Returns
b'te' # wrong
4. This is right
b'text.txt'.removesuffix(b'.txt')
Returns
b'text'
5. Once only
b'namexx'.removesuffix(b'x')
Returns
b'namex'
6. Whole
b'abc'.removesuffix(b'abc')
Returns
b''

Pitfalls

1. It removes the suffix once, not repeatedly
A doubled suffix keeps one copy. Loop if every trailing copy should go.
One copy left
b'namexx'.removesuffix(b'x')
b'namex'
Loop for all
while d.endswith(b'x'):
    d = d.removesuffix(b'x')
b'name'
2. No error when the suffix is missing
Silent no-op by design. A wrong suffix is not caught; the data just comes back unchanged.
Typo hidden
b'file.txt'.removesuffix(b'.text')
b'file.txt' # unchanged, no error
Check first
if not name.endswith(b'.txt'):
    raise ValueError('not a txt')
explicit
3. The old slice idiom breaks on an empty suffix
d[:-0] is d[:0], which is empty — so the pre-3.9 one-liner silently returns nothing when the suffix is empty. removesuffix handles that case correctly, which is one more reason to prefer it.
Empty result
s = b''
b'abc'[:-len(s)]
b'' # -0 is 0
Method is safe
b'abc'.removesuffix(b'')
b'abc'

When to use

Use it
  • Stripping a file extension or other exact trailer
  • Anywhere rstrip was being misused for an exact suffix
  • Optional suffixes where absence is normal
Reach for something else
  • Removing a SET of trailing bytes → rstrip
  • A suffix that must be present → check with endswith and raise
  • Supporting Python 3.8 or older without a fallback

Notes

Complexity
O(len(suffix)) to check, then a slice
Return
A new bytes object; the original when the suffix is absent
CPython impl
Objects/bytesobject.c :: bytes_removesuffix
Memory
Allocates the shortened result
Thread-safe
Yes — bytes are immutable

FAQ

Because rstrip strips a SET of bytes — dot, t, x — from the right, repeatedly, until a byte outside the set appears. On b"text.txt" it removes .txt and then keeps eating the t and x of "text". removesuffix matches the exact sequence once.

b'text.txt'.rstrip(b'.txt')        # b'te'
b'text.txt'.removesuffix(b'.txt')  # b'text'

History

3.9
removeprefix and removesuffix added to str, bytes and bytearray by PEP 616.