os.path.join
join puts exactly one separator between components ('/' with posixpath, '\' with ntpath) and never touches the disk. The rule that surprises people: a component that is absolute — '/etc' on POSIX, 'C:\' or '\\server\share' on Windows — throws away everything before it.
Demo
import posixpath posixpath.join(*['/home/ada', 'projects', 'site', 'index.html'])
posixpath only adds '/' where the previous component does not already end with one, so 'logs/' + '2026/' + 'app.log' gives no doubled slashes. '/etc/passwd' is absolute, so '/srv/app/static' is discarded. In the second tab ntpath uses '\' (shown doubled in the repr) and treats 'C:' as a drive: ntpath.join('C:', 'Users') is 'C:Users' — relative to the current folder on drive C, not 'C:\Users'. Both modules treat '/tmp/x' as rooted and drop 'project'.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| path | str | bytes | PathLike | yes | The first component. |
| *paths | str | bytes | PathLike | no | More components. All str or all bytes — mixing raises TypeError. An empty last component adds a trailing separator. |
Return value
str | bytes — The joined path. Nothing is checked on disk.
Common patterns
import os config = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'config.toml')
import os parts = ['data', '2026', 'report.csv'] path = os.path.join(*parts)
import os def safe_join(base, name): p = os.path.abspath(os.path.join(base, name)) if os.path.commonpath([os.path.abspath(base), p]) != os.path.abspath(base): raise ValueError('outside the base folder') return p
import posixpath key = posixpath.join('assets', 'img', 'logo.png')
Examples
Pitfalls
import posixpath posixpath.join(['data', 'raw'])
import posixpath posixpath.join(*['data', 'raw'])
import posixpath posixpath.join('static', '/img/logo.png')
import posixpath posixpath.join('static', '/img/logo.png'.lstrip('/'))
import ntpath ntpath.join('https://example.com/api', 'v1')
import posixpath posixpath.join('https://example.com/api', 'v1')
When to use
- Combining a folder and a file name you got as strings
- Code that must produce native paths on each OS
- Object paths → Path('a') / 'b' (pathlib)
- URLs → urllib.parse.urljoin (or posixpath for the path part)
- Untrusted names → validate the result (commonpath) — join will happily escape the base
Notes
FAQ
Because a later argument is absolute — it starts with '/' (or with a drive or '\' on Windows). join then starts over from that component. Strip the leading separator if it was meant to be relative.