如何使用pathlib在Python中获取两个绝对路径之间的相对路径?

14
在Python 3中,我使用pathlib定义了两个路径,比如说:
from pathlib import Path

origin = Path('middle-earth/gondor/minas-tirith/castle').resolve()
destination = Path('middle-earth/gondor/osgiliath/tower').resolve()

如何获取从origindestination的相对路径?在这个例子中,我想要一个返回../../osgiliath/tower或等效路径的函数。
理想情况下,我希望有一个名为relative_path的函数,它始终满足以下条件:
origin.joinpath(
    relative_path(origin, destination)
).resolve() == destination.resolve()

请注意,在这种情况下,Path.relative_to 是不够的,因为 origin 不是 destination 的父目录。另外,我没有使用符号链接,所以可以假设不存在符号链接,如果这样简化问题的话。
如何实现 relative_path

如果两个目录有共同的部分,是否可以用“..”来代替?只要在它之前的所有内容都是相同的。所以我认为可以按照“os.sep()”或者在你的情况下使用“/”进行分割,将每个字符串放入一个列表中,并迭代第二个列表,直到遇到与第一个列表在同一索引处不存在的元素。然后你们的路径就会分开。用“..”来替换每个迭代的元素。 - BoboDarph
3个回答

15

这是微不足道的os.path.relpath

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'

3
太好了,谢谢!我原以为pathlib有这样一个看似简单的功能,没想到还需要去其他地方找。你知道是否有一种只使用pathlib来做到这一点的方式吗?无论如何,你的解决方案很完美。 - ruancomelli
断言是依赖于操作系统的。 - Zephaniah Grunschlag
1
@ZephaniahGrunschlag 这是真的,但在这种情况下也不相关,因为断言仅用于演示 relpath 的功能。 - Adam Smith
相关性在观察者的眼中。当示例在我的系统上产生误导性结果时,我并没有觉得它不相关。 - Zephaniah Grunschlag
1
对于那些只想使用pathlib的解决方案的人,可以查看我的答案。由于它需要Python 3.12,我将保留@AdamSmith的解决方案作为被接受的答案(直到Python 3.12变得更受欢迎为止)。 - undefined
@ruancomelli 看起来根据Python的版本有两个不同的答案似乎有些愚蠢。我会将我的答案更改为社区共享,并且你应该编辑PurePath.relative_to选项。 - undefined

3
如果您想要自己编写一个Python函数将绝对路径转换为相对路径:
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

这假设:
1)start_file_pathdestination_file_path以相同的根文件夹开头。
2)斜杠类型不可互换。
3)您没有使用允许在文件名中使用斜杠的文件系统。
这些假设可能是优点或缺点,具体取决于您的用例。
缺点:如果您正在使用pathlib,则通过混合此功能会打破该模块的API流程;适用范围有限;输入必须对您正在使用的文件系统进行消毒。
优点:比@AdamSmith的答案快202倍(在Windows 7、32位上测试)。

在大多数常见情况下(a/b/c/d,a/e/f/g 的某些排列将是最常见的情况),这似乎是错误的,并且对于Windows样式的反斜杠分隔路径根本不起作用。 - Adam Smith
@AdamSmith 不确定你是否在谈论pathlib,但如果你正在使用内置的open函数,在我的Windows 7上,运行Python 3.7.2,它接受shell中和存储代码中绝对的“C:/…”和相对的“a/b/c”路径(当然要使用字面字符串来忽略转义序列)。 - Arundel
我使用Pathlib的Path进行了测试,结果表明在shell会话中,无论是绝对路径还是相对路径,它都可以接受两种类型的斜杠。 - Arundel
如果根父级不相等,那么在别人的代码中会以“无效路径”的错误方式失败,或者它可能会编辑您没有意图编辑的文件。是的,那是灾难性的。但也有速度、专业知识和用例的论点。错误检查会减慢代码。如果你是一个“专家”,你就不会犯那个错误。如果你的用例是获取未经过自己审核的未经消毒的输入,那么你的建议是有道理的。如果你的用例只涉及Windows输入,那么该函数在不检查正斜杠等情况下将更快。根据OP的情况,我可能会添加到该函数中。 - Arundel
1
@rugortal 啊,我明白了。我的函数肯定是针对有限但快速的使用情况,所以我已经发布了免责声明。 - Arundel
显示剩余8条评论

1
在Python 3.12+中,Path.relative_to接受一个名为walk_up的参数,提供了这个功能:
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")

根据关于PurePath.relative_to(other, walk_up=False)的官方文档的说明:

walk_upFalse(默认值)时,路径必须以other开头。当参数为True时,可以添加..条目来形成相对路径。在其他情况下,例如引用不同驱动器的路径,将引发ValueError异常。

对于旧版本的Python(3.11及更早版本),请查看其他答案

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接