Python's str.strip() Argument Trap: The Prefix That Eats Your Filename

2026-05-25

This function is supposed to strip a ./data/ prefix and a .json suffix from a file path. The code looks clean, reads left-to-right, and even passes a few smoke tests.

def clean_filename(path):
    """Strip './data/' prefix and '.json' extension."""
    return path.lstrip("./data/").rstrip(".json")

print(clean_filename("./data/file.json"))     # "file"   looks good
print(clean_filename("./data/report.json"))   # "repor"  ...wait
print(clean_filename("./data/users.json"))    # "user"   missing 's'
print(clean_filename("./data/dataset.json"))  # "e"      💥

The Bug

str.lstrip(chars) and str.rstrip(chars) do not remove a substring. They remove any character in the given set of characters, repeatedly, from that end of the string.

So "./data/" is interpreted as the set {'.', '/', 'd', 'a', 't'}. And ".json" becomes {'.', 'j', 's', 'o', 'n'}. Walk through "./data/dataset.json":

The function works for "file.json" by sheer luck — neither end happens to contain any "poisonous" characters beyond what we intended to strip. Every filename that does contain those characters gets silently mangled. Worst part: there's no exception, no warning. Just a corrupted string that flows downstream into database lookups, cache keys, or filenames that don't exist.

This is one of Python's most-reported "I thought it worked!" bugs, partly because the API name encourages substring intuition (we say "strip the prefix"), and partly because it often passes initial testing on tidy inputs.

The Fix

Python 3.9 added str.removeprefix() and str.removesuffix() precisely for this. They do exactly what you meant:

def clean_filename(path):
    return path.removeprefix("./data/").removesuffix(".json")

print(clean_filename("./data/dataset.json"))  # "dataset"  ✓
print(clean_filename("./data/users.json"))    # "users"    ✓

On older Pythons, use os.path for paths, pathlib.Path(p).stem for extensions, or write the slice yourself:

if path.startswith("./data/"):
    path = path[len("./data/"):]
if path.endswith(".json"):
    path = path[:-len(".json")]

The deeper lesson: lstrip/rstrip and strip are character-set operations, not substring operations. The string you pass is sugar for a set. Whenever you see them called with a multi-character argument that looks like a "word," that's a code smell — verify the author actually wanted a set, not a prefix.

Key Takeaway: str.strip("./data/") doesn't strip the substring "./data/" — it strips any of the characters ., /, d, a, t — so reach for removeprefix/removesuffix when you mean a literal prefix or suffix.

All newsletters