PurePosixPath does pure string work — no disk, no existence check, no permissions. Read what it says about the three awkward names.
from pathlib import PurePosixPath
print(PurePosixPath('archive.tar.gz').suffix) # -> '.gz' only the last
print(PurePosixPath('archive.tar.gz').stem) # -> 'archive.tar'
print(PurePosixPath('.gitignore').suffix) # -> '' it is a name
print(PurePosixPath('docs/report.txt').parent) # -> 'docs'
print(PurePosixPath('archive.tar.gz').with_suffix('.bak'))
# -> 'archive.tar.bak'
- Pure means it never touches the filesystem
PurePosixPath parses and manipulates a path as text. Nothing is opened, nothing has to exist, and it behaves identically on Windows and Linux — which is exactly what you want for a function whose job is naming, not reading. Path (without Pure) is the one that talks to the disk.
- A leading dot is a name, not an extension
PurePosixPath('.gitignore').suffix is the empty string, because the dot is the first character rather than a separator. Every split('.') solution gets this wrong and produces an empty filename. The library encodes a convention you would otherwise have to know and remember.
- with_suffix replaces the last one only
It swaps '.gz' for '.bak' and leaves '.tar' where it is, because only the final component counts as the suffix. If you wanted archive.bak you would be asking a different question — one about stems, not extensions.
Do not split on dots. The standard library already knows what an extension is, including the cases you have not thought of — find the class in pathlib that works on paths as text.
PurePosixPath(name).with_suffix('.pdf') does the replacement, directories and double extensions included. It needs the dot, so normalise the caller’s extension before you pass it in.
Guard first: an extension that is empty once its dots are stripped is a bug. Then build the suffix as '.' plus extension.lstrip('.') so both call styles work, apply with_suffix, and convert the result back to a string with str().
from pathlib import PurePosixPath def change_extension(name, extension): if not extension.strip('____'): raise ValueError(...) suffix = '.' + extension.____('.') return ____(PurePosixPath(name).____(suffix))
from pathlib import PurePosixPath
def change_extension(name, extension):
if not extension.strip("."):
raise ValueError(f"extension cannot be empty, got {extension!r}")
suffix = "." + extension.lstrip(".")
return str(PurePosixPath(name).with_suffix(suffix))
Double extensions, dotfiles, directories containing dots, paths with no extension, and mixed-case suffixes are all correct here and none of them appears in the code. That is what a good standard-library type buys you: not fewer lines, but fewer rules you have to know. The split('.') version is the same length and wrong four ways.
lstrip('.') removes leading dots so 'pdf' and '.pdf' become one case. The guard uses strip('.') instead, so a lone '.' is caught as empty — the two calls are doing different jobs and the difference is deliberate. Note lstrip takes a *set of characters*, so it removes every leading dot, not just one.
PurePosixPath never touches the disk, so this works on a filename that does not exist yet — which is the normal case when you are naming an output file. Converting back with str() means the caller gets the type they gave you rather than a path object they then have to convert.
If you are already holding a Path — because you listed a directory, say — do not convert to a string and back; call with_suffix on it directly and keep the type. Note too that with_suffix raises for a suffix that does not start with a dot and for paths with no name at all, so a version taking untrusted input needs to decide whether those should be errors or something gentler. And on Windows paths specifically, use PureWindowsPath or plain Path — PurePosixPath will treat a backslash as an ordinary character rather than a separator.
- Add remove_extension, and decide what it should do to .gitignore — the honest answer is harder than it looks.
- Handle .tar.gz as a single compound extension when the caller asks for it, and work out how you would know which double extensions count.
- Make it work for Windows paths too by choosing the right pathlib class from the input, then decide whether guessing is better than making the caller say.