Rstrip Removes Too Much of a Filename: Use an Exact Suffix Contract
By ON Lab · Sources checked November 8, 2026
rstrip(".csv") does not remove .csv as a unit. It repeatedly removes any trailing character belonging to that set. For one exact suffix on Python 3.9 or later, use removesuffix(".csv"). Compare the strings before using them to rename or write files.
A filename that exposes the difference
A name such as report.csv makes the methods appear equivalent. A basename ending in another s, c or v exposes the difference. Our Python 3.9.6 fixture compared seven strings without renaming any files.
for name in ["report.csv", "civic.csv", "class.csv", "notes.csv",
"data.CSV", "archive.tar.gz", ".csv"]:
print(repr(name), repr(name.rstrip(".csv")),
repr(name.removesuffix(".csv")))| Input | rstrip(".csv") | removesuffix(".csv") |
|---|---|---|
| report.csv | report | report |
| civic.csv | civi | civic |
| class.csv | cla | class |
| notes.csv | note | notes |
| data.CSV | data.CSV | data.CSV |
| archive.tar.gz | archive.tar.gz | archive.tar.gz |
| .csv | Empty string | Empty string |
The set includes the dot and all three letters. Stripping stops at the first trailing character outside that set, not at an extension boundary. Reordering or duplicating the argument’s characters does not turn it into a literal suffix.
Choose the text contract explicitly
For an exact case-sensitive suffix, removesuffix returns the original string when the suffix is absent. It removes one matching occurrence. If your contract requires a particular suffix, validate that requirement separately:
suffix = ".csv"
name = "civic.csv"
if not name.endswith(suffix):
raise ValueError(f"Expected {suffix!r}: {name!r}")
print(name.removesuffix(suffix))The method does not enforce an input schema by itself. Decide whether a nonmatching name should pass through or be rejected. A case-insensitive selection rule also needs an explicit policy; lowercasing an entire path can change the proposed output name.
When Path.stem is the real operation
If you mean the final path component without its final suffix, inspect Path.stem. Our fixture gave archive.tar for Path("archive.tar.gz").stem, while removing an exact .csv suffix left that input unchanged. These operations are different.
The single leading-dot name .csv is another boundary. Exact removal makes it empty, while pathlib’s stem handling treats that leading-dot name differently. Decide whether that name is valid before constructing a destination. A successful string operation does not certify an acceptable filename.
For older Python, guard the empty suffix
removesuffix was introduced in Python 3.9. A maintained older interpreter can express the narrow operation with a guarded slice:
def remove_suffix(text, suffix):
if suffix and text.endswith(suffix):
return text[:-len(suffix)]
return textThe nonempty condition matters because [:-0] produces an empty string. Test a match, a nonmatch and an empty suffix. A helper is unnecessary on an interpreter already supplying the method, but it makes the compatibility boundary explicit.
Preview names before filesystem changes
Generate a source-to-proposed-name table. Check empty destinations, collisions and names differing only by case on the target filesystem. Do not rename while still discovering these problems. These examples stop at text output; they do not implement transactional batch renaming or a collision policy.
The CPython discussion describes repeated strip-method confusion and possible diagnostics. It does not make rstrip invalid: removing a set of trailing characters is useful when that is the intention. Official documentation and free linters are alternatives. This guide adds the filename matrix and the difference from pathlib’s stem operation.
Method: Python 3.13 documentation and a historical CPython discussion, plus seven synthetic strings on Python 3.9.6/macOS. No real files, case-insensitive rename, linter installation or automatic repair was tested.