Library code that only works with one kind of path frustrates users and breaks in surprising ways.
Callers will pass strings, Path objects, and occasionally other os.PathLike objects (from libraries like py.path or custom classes). A robust function accepts all of them:
def load(path: str | os.PathLike[str]) -> dict:
path = Path(path)Rules of thumb:
- Normalise at the boundary: convert once with
Path(path)at the top, then work with Path internally. - If you need a string (for a C library or a subprocess argument), use
os.fspath(path)rather thanstr(path).fspathaccepts only str, bytes and PathLike and raisesTypeErrorfor other objects, whereasstr()happily turnsNoneor an int into a bogus path. - Type hints:
str | os.PathLike[str]is the common annotation; typeshed'sStrPathalias means the same. - Return Path objects from functions that produce paths; they convert to strings trivially.
- Don't accept bytes paths unless you really need to handle undecodable names; mixing str and bytes in one join raises
TypeError.
Avoid isinstance(x, str) checks to decide whether something is a path: a str might be file contents. Make two clearly named parameters or functions instead (load_file(path) vs loads(text)), the way json does.
Example
import os
from pathlib import Path
def word_count(path: str | os.PathLike[str]) -> int:
path = Path(path) # normalise once
return len(path.read_text(encoding="utf-8").split())
class Asset:
def __init__(self, name): self.name = name
def __fspath__(self): return f"assets/{self.name}"
Path("assets").mkdir()
Path("assets/a.txt").write_text("one two three", encoding="utf-8")
print(word_count("assets/a.txt")) # 3
print(word_count(Path("assets", "a.txt"))) # 3
print(word_count(Asset("a.txt"))) # 3
print(str(None)) # None -- a bogus "path"
try:
os.fspath(None)
except TypeError as e:
print(type(e).__name__) # TypeErrorRemember
- Accept
str | os.PathLike[str]and convert withPath(...)at the boundary - Use
os.fspathinstead ofstr()when you need a string path - Return Path objects; don't overload one parameter for both paths and contents
Watch out for
- Using
str(path)for conversion, which turns a stray None into a file literally named 'None'.