File extensions show up in filtering uploads, routing parsers, and building output paths. In modern Python, pathlib.Path is usually the clearest tool; os.path.splitext remains useful on plain strings. This guide walks through those APIs, double extensions such as .tar.gz, practical checks, odd filenames, mistakes to avoid, and a short cheat sheet.
Tested on: Python 3.13.3; kernel 6.14.0-37-generic.
Method 1: Get file extension using pathlib
pathlib is in the standard library (since Python 3.4). Build a Path from a string; .suffix returns the last extension including the leading dot, or an empty string when there is none. The related property Path.stem is the final path segment with the last suffix removed (for report.csv, stem is report).
Example (filename only):
from pathlib import Path
name = Path("report.csv")
print(name.suffix)Running that prints .csv (with the dot).
Example (full path):
from pathlib import Path
path = Path("/home/user/project/data/report.csv")
print(path.suffix)
print(path.name)The suffix is still .csv; path.name is report.csv.
Example (case-insensitive check):
Comparisons should normalize case, because Path("data.CSV").suffix is .CSV, not .csv.
from pathlib import Path
path = Path("Report.CSV")
if path.suffix.lower() == ".csv":
print("Treat as CSV")Method 2: Get file extension using os.path.splitext
os.path.splitext takes a path string and returns (root, ext) where ext is the part from the last dot onward, including the dot. If there is no extension, ext is ''.
Example (split basename):
import os
root, ext = os.path.splitext("document.txt")
print(root, ext)That prints document and .txt.
Example (path with directories):
import os
path = "/var/log/nginx/access.log.1"
root, ext = os.path.splitext(path)
print("root:", root)
print("extension:", ext)Here ext is .1 (only the final segment after the last dot). For logs like access.log, you get .log; if you need the “double” story, use Method 3.
Method 3: Get multiple extensions like .tar.gz
Path.suffix only reflects the last piece: for archive.tar.gz it is .gz. Use Path.suffixes for every dotted suffix segment.
Example (last suffix only):
from pathlib import Path
path = Path("archive.tar.gz")
print(path.suffix)Output shape: .gz.
Example (all suffix segments):
from pathlib import Path
path = Path("archive.tar.gz")
print(path.suffixes)That yields a list like ['.tar', '.gz']. For document.v2.0.txt, suffixes is ['.v2', '.0', '.txt']—each dotted chunk after the first dot in the basename is modeled as its own suffix, so treat suffixes as “split on dots in the name,” not always “semantic version triple.”
os.path.splitext on archive.tar.gz returns ('archive.tar', '.gz'), same “last extension only” rule as Path.suffix.
Method 4: Check file extension in Python
Use normalized suffix checks instead of substring hacks. Extension checks belong next to other validation (size, MIME, permissions) when security matters.
Example (single allowed type):
from pathlib import Path
path = Path("export.csv")
if path.suffix.lower() == ".csv":
print("CSV branch")Example (allow-list):
from pathlib import Path
allowed = {".csv", ".tsv", ".txt"}
path = Path("notes.TXT")
if path.suffix.lower() in allowed:
print("Allowed text-like type")For more on existence checks before opening files, see check if a file exists. For writing outputs, see write to file in Python. CSV routing often pairs extension checks with read and write CSV.
Names without a dot in the basename (README) and typical dotfiles (.gitignore) yield an empty suffix and an empty suffixes list. For those, os.path.splitext still returns a pair: ('README', '') and ('.gitignore', '')—the extension slot is empty, not missing. Multiple dots in one name still give a single final suffix (for example .txt on document.v2.0.txt); see Method 3 for how suffixes breaks that basename into segments.
Common mistakes
-
Using
split(".")for every filename —name.split(".")[-1]drops the leading dot, breaks on paths with directories, and mishandles names with no dot (the whole string becomes the “extension”). PreferPath.suffixoros.path.splitext. -
Forgetting that the extension includes the dot —
suffixandsplitextgive.csv, notcsv. Comparisons likesuffix == "csv"silently fail. -
Trusting the extension as full file validation — extensions are hints only. For uploads or security-sensitive flows, combine size limits, allow-lists, and content checks (for example
mimetypesor reading magic bytes); do not rely on a renamed.exeposing as.txt.
Python file extension cheat sheet
| Goal | Typical approach |
|---|---|
| Last extension with dot | Path(name).suffix or os.path.splitext(name)[1] |
| All dotted suffix segments | Path(name).suffixes |
| Root + ext tuple | os.path.splitext(path) |
| Name without last suffix | Path(name).stem (for a.b.txt → a.b) |
| Case-insensitive rule | path.suffix.lower() in {".csv", ".tsv"} |
| No extension in basename | suffix is ''; splitext second part is '' |
Dotfile like .gitignore |
Expect empty suffix; use full name rules if needed |
Official references: pathlib.Path, os.path.splitext.
Summary
Use pathlib.Path.suffix for the final extension (including the dot), Path.stem when you need the filename with that suffix stripped once, suffixes when double endings such as .tar.gz matter, and os.path.splitext when you want a (root, ext) pair on strings. Normalize with .lower() for comparisons, remember dotfiles often have an empty suffix, and avoid split(".") for production path logic. Extension checks are not a substitute for real content validation on untrusted files. Related: split a string, try / except.

