PurePath.joinpath
p / "a" / "b" and p.joinpath("a", "b") are the same thing. Both follow os.path.join: separators are inserted for you, extra slashes vanish, and any absolute segment throws away what came before it.
Common call
base / 'sub' / 'file.txt'
Returns
a new path; str / path works too
Replaces
os.path.join(base, "sub", "file.txt")
Watch out
base / '/etc' is just /etc
PurePath.joinpath(*pathsegments)
→ PurePath
Demo
Live evaluation
Append one segment.
Try:
Inputs
basestrleft side
childstrright side
Code
from pathlib import PurePosixPath PurePosixPath('/srv/app') / 'static/logo.svg'
Result
PurePosixPath('/srv/app/static/logo.svg')
An absolute segment resets the path: '/srv/app' / '/etc/passwd' is '/etc/passwd', which is why joining user input onto a base folder is not a safety check. An empty segment adds nothing, and '..' is kept literally. In the last joinpath case '/src/' starts with a slash, so 'project/' is discarded.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| *pathsegments | str | os.PathLike | no | Segments to append, in order. Each may contain separators itself. |
Return value
PurePath — A new path of the same class.
Common patterns
Join a list of segments
Unpack the list into joinpath.
from pathlib import Path parts = ['2026', '09', 'report.csv'] p = Path('archive').joinpath(*parts)
Keep user-supplied names inside a folder
Join, resolve, then verify the result is still under the base.
from pathlib import Path base = Path('uploads').resolve() target = (base / user_name).resolve() if not target.is_relative_to(base): raise ValueError('path escapes the upload folder')
Subclass that survives joining (3.12+)
Override with_segments to pass extra state to derived paths.
from pathlib import Path class TaggedPath(Path): def __init__(self, *args, tag=None): super().__init__(*args) self.tag = tag def with_segments(self, *pathsegments): return type(self)(*pathsegments, tag=self.tag)
Examples
1. Chaining /
from pathlib import PurePosixPath
PurePosixPath('/srv') / 'www' / 'index.html'
Returns
PurePosixPath('/srv/www/index.html')2. joinpath with several segments
from pathlib import PurePosixPath
PurePosixPath('/srv').joinpath('www', 'index.html')
Returns
PurePosixPath('/srv/www/index.html')3. A str on the left
from pathlib import PurePosixPath
'/srv' / PurePosixPath('www')
Returns
PurePosixPath('/srv/www')4. Joining two paths
from pathlib import PurePosixPath
PurePosixPath('a/b') / PurePosixPath('c/d')
Returns
PurePosixPath('a/b/c/d')5. An absolute segment wins
from pathlib import PurePosixPath
PurePosixPath('/usr').joinpath('/etc', 'hosts')
Returns
PurePosixPath('/etc/hosts')6. Non-path operands are rejected
from pathlib import PurePosixPath
PurePosixPath('logs') / 2026
Returns
TypeError: unsupported operand type(s) for /: 'PurePosixPath' and 'int'7. with_segments builds same-class paths
from pathlib import PurePosixPath
PurePosixPath('/x').with_segments('a', 'b')
Returns
PurePosixPath('a/b')Pitfalls
1. Joining a number
Segments must be str or path-like; format numbers yourself.
/ int
from pathlib import PurePosixPath PurePosixPath('logs') / 2026
TypeError: unsupported operand type(s) for /: 'PurePosixPath' and 'int'
/ str(int)
from pathlib import PurePosixPath PurePosixPath('logs') / str(2026)
PurePosixPath('logs/2026')
2. Leading slash on the second segment
'/static' is absolute, so it replaces the base. Strip the slash, or join the parts individually.
/ '/static'
from pathlib import PurePosixPath PurePosixPath('/srv/app') / '/static/logo.svg'
PurePosixPath('/static/logo.svg')
lstrip('/')
from pathlib import PurePosixPath PurePosixPath('/srv/app') / '/static/logo.svg'.lstrip('/')
PurePosixPath('/srv/app/static/logo.svg')
3. Using + to join
Paths do not concatenate with +; / is the join operator.
path + str
from pathlib import PurePosixPath PurePosixPath('/srv') + '/www'
TypeError: unsupported operand type(s) for +: 'PurePosixPath' and 'str'
path / str
from pathlib import PurePosixPath PurePosixPath('/srv') / 'www'
PurePosixPath('/srv/www')
When to use
Use it
- Building any path from pieces
- joinpath(*parts) when the segments are in a list
Reach for something else
- Security checks on user input → resolve() + is_relative_to()
- Changing the extension or file name → with_suffix / with_name
Notes
CPython impl
PurePath.joinpath and __truediv__ both call self.with_segments(self, *segments); the parsing that follows uses os.path.join rules (posixpath.join / ntpath.join)
with_segments
Added in 3.12: every derived path (/, parent, iterdir(), glob() …) is created through it, so subclasses can override it to carry state
__rtruediv__
'a' / PurePosixPath('b') works because str has no __truediv__, so Python falls back to the path's __rtruediv__
FAQ
Use the / operator: Path('data') / 'raw' / 'file.csv'. joinpath('raw', 'file.csv') does the same with several segments in one call.