os.path.normpath

normpath is pure string work: it never looks at the disk, so 'link/..' is removed even if link is a symlink pointing elsewhere. Leading '..' of a relative path are kept; on POSIX exactly two leading slashes are kept too. normcase is for comparisons: identity on POSIX, lower case with backslashes on Windows.

os.path functionPython 3.0+Live demo
Common call
os.path.normpath(path)
Returns
'/usr/local/lib'
Replaces
Hand-written replace('//', '/') and '..' handling
Watch out
'a/link/..' becomes 'a' even when link is a symlink — use realpath to follow links
os.path.normpath(path) / os.path.normcase(path)
→ str | bytes

Demo

Live evaluation
POSIX rules through posixpath.
Try:
Inputs
pathstra messy POSIX path
Code
import posixpath
posixpath.normpath('/usr//local/./bin/../lib/')
Result
'/usr/local/lib'

'bin/..' cancels out, '.' and the doubled and trailing slashes disappear. A relative path cannot go above its start, so normpath keeps the leading '..' parts; above the root there is nothing, so '/../etc' becomes '/etc'. An empty path becomes '.'. ntpath also turns every / into \. POSIX leaves exactly two leading slashes alone (their meaning is implementation-defined), while ntpath reads '//srv/x' as the UNC share \\srv\x.

Parameters

NameTypeRequiredDescription
pathstr | bytes | PathLikeyesAny path. Nothing is checked on disk.

Return value

str | bytes — The normalized path ('.' for an empty result).

Common patterns

Compare two paths textually
normcase(normpath(...)) on both sides — correct on Windows too.
import os
same = os.path.normcase(os.path.normpath(a)) == os.path.normcase(os.path.normpath(b))
Last folder of a path with a trailing slash
normpath removes the trailing separator.
import os
name = os.path.basename(os.path.normpath('/var/log/'))
Real location, following symlinks
When links matter, use realpath instead.
import os
canonical = os.path.realpath(path)

Examples

1. Clean up a messy path
import posixpath posixpath.normpath('/usr//local/./bin/../lib/')
Returns
'/usr/local/lib'
2. Leading '..' stay
import posixpath posixpath.normpath('../a/../../b')
Returns
'../../b'
3. Empty path
import posixpath posixpath.normpath('')
Returns
'.'
4. Two leading slashes are kept (POSIX)
import posixpath (posixpath.normpath('//srv/x'), posixpath.normpath('///srv/x'))
Returns
('//srv/x', '/srv/x')
5. ntpath converts slashes
import ntpath ntpath.normpath('C:/Users//ada/./docs/../x.txt')
Returns
'C:\\Users\\ada\\x.txt'
6. normcase: POSIX vs Windows
import posixpath, ntpath (posixpath.normcase('ReadMe.TXT'), ntpath.normcase('C:/Users/ReadMe.TXT'))
Returns
('ReadMe.TXT', 'c:\\users\\readme.txt')
7. Equal after normalizing
import posixpath posixpath.normpath('a/b') == posixpath.normpath('a/./b/')
Returns
True

Pitfalls

1. Using normpath to sanitize user input
normpath happily keeps leading '..' — it does not confine a path to a folder. Check the result with commonpath.
normpath only
import posixpath
posixpath.normpath(posixpath.join('uploads', '../../etc/passwd'))
'../etc/passwd'
verify containment
import posixpath
base = '/srv/uploads'
p = posixpath.normpath(posixpath.join(base, '../../etc/passwd'))
posixpath.commonpath([base, p]) == base
False
2. Comparing Windows paths without normcase
Windows file names are case-insensitive and accept both slashes.
normpath only
import ntpath
ntpath.normpath('C:/Data/A.txt') == ntpath.normpath(r'c:\data\a.txt')
False
normcase too
import ntpath
ntpath.normcase(ntpath.normpath('C:/Data/A.txt')) == ntpath.normcase(ntpath.normpath(r'c:\data\a.txt'))
True

When to use

Use it
  • Displaying or comparing paths built by concatenation
  • Removing trailing separators and redundant parts
Reach for something else
  • Resolving symlinks → os.path.realpath
  • Making a path absolute → os.path.abspath (which calls normpath)
  • Security checks on their own → combine with abspath + commonpath

Notes

CPython impl
Both modules have a C implementation in Modules/posixmodule.c (_path_normpath) with a pure-Python fallback that gives the same results
Symlinks
normpath may change the meaning of a path with symlinks: if b is a link, a/b/.. is not necessarily a
normcase
posixpath.normcase returns the path unchanged (macOS file systems are usually case-insensitive too, but normcase does not account for that)

FAQ

It rewrites a path string into a canonical form: A//B, A/B/, A/./B and A/foo/../B all become A/B. On Windows it also turns / into \. It does not access the file system.