Reputation: 730
In Python 3, I defined two paths using pathlib
, say:
from pathlib import Path
origin = Path('middle-earth/gondor/minas-tirith/castle').resolve()
destination = Path('middle-earth/gondor/osgiliath/tower').resolve()
How can I get the relative path that leads from origin
to destination
? In this example, I'd like a function that returns ../../osgiliath/tower
or something equivalent.
Ideally, I'd have a function relative_path
that always satisfies
origin.joinpath(
relative_path(origin, destination)
).resolve() == destination.resolve()
Note that Path.relative_to
is insufficient in this case since origin
is not a destination
's parent. Also, I'm not working with symlinks, so it's safe to assume there are none if this simplifies the problem.
How can relative_path
be implemented?
Upvotes: 19
Views: 5088
Reputation: 54163
In Python 3.12+, Path.relative_to
takes a parameter called walk_up
which provides exactly this functionality:
from pathlib import Path
origin = Path('middle-earth/gondor/minas-tirith/castle')
destination = Path('middle-earth/gondor/osgiliath/tower')
assert destination.relative_to(origin, walk_up=True) == Path("../../osgiliath/tower")
According to the official documentation for PurePath.relative_to(other, walk_up=False)
:
When
walk_up
isFalse
(the default), the path must start withother
. When the argument isTrue
,..
entries may be added to form the relative path. In all other cases, such as the paths referencing different drives,ValueError
is raised.
For older Python versions (3.11 and older), os.path.relpath
can be used instead:
import os.path
from pathlib import Path
origin = Path('middle-earth/gondor/minas-tirith/castle').resolve()
destination = Path('middle-earth/gondor/osgiliath/tower').resolve()
assert os.path.relpath(destination, start=origin) == '..\\..\\osgiliath\\tower'
Note that the value returned from os.path.relpath
is a string, not a pathlib.Path
instance.
Upvotes: 19
Reputation: 125
If you'd like your own Python function to convert an absolute path to a relative path:
def absolute_file_path_to_relative(start_file_path, destination_file_path):
return (start_file_path.count("/") + start_file_path.count("\\") + 1) * (".." + ((start_file_path.find("/") > -1) and "/" or "\\")) + destination_file_path
This assumes that:
1) start_file_path
starts with the same root folder as destination_file_path
.
2) Types of slashes don't occur interchangably.
3) You're not using a filesystem that permits slashes in the file name.
Those assumptions may be an advantage or disadvantage, depending on your use case.
Disadvantages: if you're using pathlib, you'll break that module's API flow in your code by mixing in this function; limited use cases; inputs have to be sterile for the filesystem you're working with.
Advantages: runs 202x faster than @AdamSmith's answer (tested on Windows 7, 32-bit)
Upvotes: 3