Code of the Day
AdvancedCross-Platform

Path portability

Why string path concatenation breaks across platforms and how pathlib.Path keeps your code correct on Linux, macOS, and Windows.

UtilitiesAdvanced6 min read
By the end of this lesson you will be able to:
  • Explain why string path concatenation fails on Windows
  • Use pathlib.Path and the / operator for all path construction
  • Use Path.home() and Path.cwd() for user-relative and working-directory-relative paths
  • Use platformdirs to locate the correct config and cache directories on each OS

The most common source of Windows breakage in Python tools written on Linux is path construction. 'data/' + 'file.csv' produces a forward-slash path that Windows cannot always parse correctly. String concatenation with os.path.join works but is verbose. pathlib.Path replaces both approaches with an operator that is correct on every platform by construction.

The separator problem

Windows uses \ as a path separator; Linux and macOS use /. Python's string operations know nothing about this distinction:

# Breaks on Windows — produces 'data/file.csv' which Windows tolerates in some
# contexts but fails in others, and '\' is never produced for subdirectories.
path = 'data/' + 'file.csv'

# Also fragile — you must remember every edge case.
path = os.path.join('data', 'subdir', 'file.csv')

pathlib.Path uses the correct separator for the current OS automatically:

from pathlib import Path

path = Path('data') / 'subdir' / 'file.csv'
# On Linux/macOS: data/subdir/file.csv
# On Windows:     data\subdir\file.csv

The / operator on Path objects is path joining, not division. It reads naturally and handles every separator edge case correctly.

Absolute paths for the user's home directory

Path.home() returns the user's home directory on every platform without inspecting environment variables or hard-coding paths:

config_file = Path.home() / '.config' / 'mytool' / 'settings.toml'

On Linux this produces /home/alice/.config/mytool/settings.toml; on macOS /Users/alice/.config/mytool/settings.toml; on Windows C:\Users\alice\.config\mytool\settings.toml. The code is identical on all three.

Path.cwd() returns the current working directory, equivalent to os.getcwd() but returning a Path object ready for further composition.

Platform-correct config and cache directories with platformdirs

Placing config files in ~/.config is correct on Linux but not on macOS (which prefers ~/Library/Application Support) or Windows (%APPDATA%). The platformdirs library handles this without any conditional logic:

from platformdirs import user_config_dir, user_cache_dir

config_dir = Path(user_config_dir("mytool", "MyOrg"))
cache_dir  = Path(user_cache_dir("mytool", "MyOrg"))

config_dir.mkdir(parents=True, exist_ok=True)
Platformuser_config_dir result
Linux~/.config/mytool
macOS~/Library/Application Support/mytool
WindowsC:\Users\alice\AppData\Roaming\MyOrg\mytool

Install platformdirs with pip install platformdirs. It is a zero-dependency package and is already a transitive dependency of many common tools, so it is rarely an additional install in practice.

Use Path objects throughout your code — open files with open(path), pass paths to other functions as Path objects, and avoid converting back to strings unless a third-party library explicitly requires it. If it does, call str(path); do not rebuild the string by hand.

Practical rule

One simple rule covers most cases: never construct a path with string concatenation or f-strings. Use Path(...) / part / part for every path, and Path.home() or platformdirs for any path that depends on the user's environment. That is the entire discipline of path portability.

Where to go next

Next: platform-specific behaviour — case sensitivity, line endings, signals, and how to detect the current OS when conditional logic is genuinely unavoidable.

Finished reading? Mark it complete to track your progress.

On this page