【问题标题】:Using pathlib's relative_to for directories on the same level将 pathlib 的 relative_to 用于同一级别的目录
【发布时间】:2016-06-28 18:20:25
【问题描述】:

python 库pathlib 提供Path.relative_to。如果一个路径是另一个路径的子路径,则此函数可以正常工作,如下所示:

from pathlib import Path
foo = Path("C:\\foo")
bar = Path("C:\\foo\\bar")
bar.relative_to(foo)

> WindowsPath('bar')

但是,如果两条路径在同一级别relative_to 不起作用。

baz = Path("C:\\baz")
foo.relative_to(baz)

> ValueError: 'C:\\foo' does not start with 'C:\\baz'

我希望结果是

WindowsPath("..\\baz")

os.path.relpath 函数正确地做到了这一点:

import os
foo = "C:\\foo"
bar = "C:\\bar"
os.path.relpath(foo, bar)

> '..\\foo'

有没有办法使用pathlib.Path实现os.path.relpath的功能?

【问题讨论】:

  • 你解决过这个问题吗?我遇到了同样的问题。我想尽可能在​​ os.path 上使用 pathlib 进行标准化,但是这个问题让我很难过。
  • @Phil 在这种情况下你似乎不得不回到os.path.relpath :( ...似乎pathlib 模块没有被认为是os.path 的替代品: (。或者你有没有找到pathlib-only 的解决方案?
  • 不,我还没有找到pathlib-only 解决方案。

标签: python pathlib


【解决方案1】:

这让我很烦,所以这是一个仅 pathlib 的版本,我认为它可以与 os.path.relpath 一样。

def relpath(path_to, path_from):
    path_to = Path(path_to).resolve()
    path_from = Path(path_from).resolve()
    try:
        for p in (*reversed(path_from.parents), path_from):
            head, tail = p, path_to.relative_to(p)
    except ValueError:  # Stop when the paths diverge.
        pass
    return Path('../' * (len(path_from.parents) - len(head.parents))).joinpath(tail)

【讨论】:

  • 只是一个挑剔,如果任何路径是符号链接,这将导致不同的结果。
【解决方案2】:

第一部分解决了 OP 的问题,但如果像我一样,他真的想要相对于公共根的解决方案,那么第二部分会为他解决。第三部分描述了我最初是如何处理它的,并且出于兴趣而保留。

相对路径

最近,与 Python 3.4-6 一样,os.path 模块已扩展为接受 pathlib.Path 对象。但是在以下情况下,它不会返回 Path 对象,而是强制包装结果。

foo = Path("C:\\foo")
baz = Path("C:\\baz")
Path(os.path.relpath(foo, baz))

> Path("..\\foo")

公共路径

我的怀疑是你真的在寻找相对于公共根的路径。如果是这种情况,来自EOL 的以下内容更有用

Path(os.path.commonpath([foo, baz]))

> Path('c:/root')

常用前缀

在遇到os.path.commonpath 之前,我使用过os.path.comonprefix

foo = Path("C:\\foo")
baz = Path("C:\\baz")
baz.relative_to(os.path.commonprefix([baz,foo]))

> Path('baz')

但请注意,您不应该在这种情况下使用它(请参阅commonprefix : Yes, that old chestnut

foo = Path("C:\\route66\\foo")
baz = Path("C:\\route44\\baz")
baz.relative_to(os.path.commonprefix([baz,foo]))

> ...
> ValueError : `c:\\route44\baz` does not start with `C:\\route`

而是来自J. F. Sebastian的以下一个。

Path(*os.path.commonprefix([foo.parts, baz.parts]))

> Path('c:/root')

...或者如果您觉得冗长...

from itertools import takewhile
Path(*[set(i).pop() for i in (takewhile(lambda x : x[0]==x[1], zip(foo.parts, baz.parts)))])

【讨论】:

    【解决方案3】:

    @Brett_Ryland's relpath 的递归版本,用于 pathlib。我发现这更具可读性,并且在大多数情况下它会在第一个 try 上成功,因此它应该具有与原始 relative_to 函数相似的性能:

    def relative(target: Path, origin: Path):
        """ return path of target relative to origin """
        try:
            return Path(target).resolve().relative_to(Path(origin).resolve())
        except ValueError as e: # target does not start with origin
            # recursion with origin (eventually origin is root so try will succeed)
            return Path('..').joinpath(relative(target, Path(origin).parent))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-12-18
      • 1970-01-01
      • 1970-01-01
      • 2015-01-19
      • 1970-01-01
      • 2014-11-13
      • 1970-01-01
      • 2018-02-05
      相关资源
      最近更新 更多