有没有一种方便的方法可以将文件 uri 映射到 os.path?

Posted

技术标签:

【中文标题】有没有一种方便的方法可以将文件 uri 映射到 os.path?【英文标题】:Is there a convenient way to map a file uri to os.path? 【发布时间】:2011-08-24 01:40:45 【问题描述】:

我无法控制的子系统坚持以 uri 的形式提供文件系统路径。是否有 python 模块/函数可以将该路径转换为文件系统所需的适当形式,以独立于平台的方式?

【问题讨论】:

你要做的不仅仅是阅读它吗? 不,我想将该 uri 或等效形式传递到 python 模块中以进行路径操作 【参考方案1】:

使用urllib.parse.urlparse从URI中获取路径:

import os
from urllib.parse import urlparse
p = urlparse('file://C:/test/doc.txt')
final_path = os.path.abspath(os.path.join(p.netloc, p.path))

【讨论】:

@JakobBowyer - 第二行中的 .path 应该被删除,否则你只是返回一个字符串到变量 p 而不是你需要在第三行处理的元组。 C:\test\doc.txt 的有效文件 URI 是 file:///C:/test/doc.txt 而不是 file://C:/test/doc.txt - 请参阅 IETF RFC 8089: The "file" URI Scheme / 2. Syntax 并在最近的 python 3 import pathlib; print(pathlib.PureWindowsPath("C:\\test\\doc.txt").as_uri()) 中运行它,所以这个答案不准确。【参考方案2】:

@Jakob Bowyer 的解决方案不会将 URL encoded characters 转换为常规 UTF-8 字符。为此,您需要使用urllib.parse.unquote

>>> from urllib.parse import unquote, urlparse
>>> unquote(urlparse('file:///home/user/some%20file.txt').path)
'/home/user/some file.txt'

【讨论】:

@IwanAucamp 你能解释一下原因吗? 考虑使用urllib.parse.unquote_plus,即“类似于unquote(),但也将加号替换为空格”。 @Boris 因为为 windows 文件 URI 返回的路径以斜杠开头 unquote(urlparse('file:///C:/Program Files/Steam/').path) -> '/C:/Program Files/Steam/' 非常适合我的 linux 环境【参考方案3】:

到目前为止,在所有答案中,我发现没有一个可以捕捉边缘情况、不需要分支、2/3 兼容、跨平台。

简而言之,这可以完成工作,只使用内置函数:

try:
    from urllib.parse import urlparse, unquote
    from urllib.request import url2pathname
except ImportError:
    # backwards compatability
    from urlparse import urlparse
    from urllib import unquote, url2pathname


def uri_to_path(uri):
    parsed = urlparse(uri)
    host = "00mnt0".format(os.path.sep, mnt=parsed.netloc)
    return os.path.normpath(
        os.path.join(host, url2pathname(unquote(parsed.path)))
    )

棘手的一点(我发现)是在 Windows 中使用指定主机的路径工作时。这在 Windows 之外不是问题:*NIX 中的网络位置只能通过路径被挂载到文件系统的根目录后访问。

来自Wikipedia: 文件 URI 采用 file://host/path 的形式,其中 host 是可访问路径的系统的完全限定域名 [...]。如果host省略,则取“localhost”。

考虑到这一点,我制定了一条规则,在将路径传递给 os.path.abspath 之前,始终使用 urlparse 提供的 netloc 作为路径前缀,这是必要,因为它删除任何由此产生的多余斜线(os.path.normpath,它也声称可以修复斜线,在 Windows 中可能有点过分热心,因此使用abspath)。

转换中的另一个关键组件是使用unquote 转义/解码 URL 百分比编码,否则您的文件系统将无法理解。同样,这在 Windows 上可能是一个更大的问题,它允许在路径中使用诸如 $空格 之类的内容,这些内容将被编码在文件 URI 中。

对于演示:

import os
from pathlib import Path   # This demo requires pip install for Python < 3.4
import sys
try:
    from urllib.parse import urlparse, unquote
    from urllib.request import url2pathname
except ImportError:  # backwards compatability:
    from urlparse import urlparse
    from urllib import unquote, url2pathname

DIVIDER = "-" * 30

if sys.platform == "win32":  # WINDOWS
    filepaths = [
        r"C:\Python27\Scripts\pip.exe",
        r"C:\yikes\paths with spaces.txt",
        r"\\localhost\c$\WINDOWS\clock.avi",
        r"\\networkstorage\homes\rdekleer",
    ]
else:  # *NIX
    filepaths = [
        os.path.expanduser("~/.profile"),
        "/usr/share/python3/py3versions.py",
    ]

for path in filepaths:
    uri = Path(path).as_uri()
    parsed = urlparse(uri)
    host = "00mnt0".format(os.path.sep, mnt=parsed.netloc)
    normpath = os.path.normpath(
        os.path.join(host, url2pathname(unquote(parsed.path)))
    )
    absolutized = os.path.abspath(
        os.path.join(host, url2pathname(unquote(parsed.path)))
    )
    result = ("DIVIDER"
              "\norig path:       \tpath"
              "\nconverted to URI:\turi"
              "\nrebuilt normpath:\tnormpath"
              "\nrebuilt abspath:\tabsolutized").format(**locals())
    print(result)
    assert path == absolutized

结果(WINDOWS):

------------------------------
orig path:              C:\Python27\Scripts\pip.exe
converted to URI:       file:///C:/Python27/Scripts/pip.exe
rebuilt normpath:       C:\Python27\Scripts\pip.exe
rebuilt abspath:        C:\Python27\Scripts\pip.exe
------------------------------
orig path:              C:\yikes\paths with spaces.txt
converted to URI:       file:///C:/yikes/paths%20with%20spaces.txt
rebuilt normpath:       C:\yikes\paths with spaces.txt
rebuilt abspath:        C:\yikes\paths with spaces.txt
------------------------------
orig path:              \\localhost\c$\WINDOWS\clock.avi
converted to URI:       file://localhost/c%24/WINDOWS/clock.avi
rebuilt normpath:       \localhost\c$\WINDOWS\clock.avi
rebuilt abspath:        \\localhost\c$\WINDOWS\clock.avi
------------------------------
orig path:              \\networkstorage\homes\rdekleer
converted to URI:       file://networkstorage/homes/rdekleer
rebuilt normpath:       \networkstorage\homes\rdekleer
rebuilt abspath:        \\networkstorage\homes\rdekleer

结果 (*NIX):

------------------------------
orig path:              /home/rdekleer/.profile
converted to URI:       file:///home/rdekleer/.profile
rebuilt normpath:       /home/rdekleer/.profile
rebuilt abspath:        /home/rdekleer/.profile
------------------------------
orig path:              /usr/share/python3/py3versions.py
converted to URI:       file:///usr/share/python3/py3versions.py
rebuilt normpath:       /usr/share/python3/py3versions.py
rebuilt abspath:        /usr/share/python3/py3versions.py

【讨论】:

根据documentation url2pathname 使用unquote 所以url2pathname(parsed.path) 应该足够了 当编码的路径名包含类似 urlencode 的字符时,您的解决方案会中断。例如。文件名foo%20bar.baz 将被您的解决方案正确编码为foo%2520bar.baz,但错误地解码为foo bar.baz。正如@dshanahan 所指出的,这是因为url2pathname 内部的unquote 不匹配【参考方案4】:

用 python 将文件 uri 转换为路径(特定于 3,如果有人真的需要,我可以为 python 2 制作):

    urllib.parse.urlparse解析uri

    urllib.parse.unquote取消引用已解析uri的路径组件

    然后...

一个。如果 path 是 windows 路径并且以 / 开头:去掉不带引号的路径组件的第一个字符(file:///C:/some/file.txt 的路径组件是 /C:/some/file.txt,它不会被 pathlib.PureWindowsPath 解释为等同于 C:\some\file.txt

b.否则,请按原样使用未加引号的路径组件。

这是一个执行此操作的函数:

import urllib
import pathlib

def file_uri_to_path(file_uri, path_class=pathlib.PurePath):
    """
    This function returns a pathlib.PurePath object for the supplied file URI.

    :param str file_uri: The file URI ...
    :param class path_class: The type of path in the file_uri. By default it uses
        the system specific path pathlib.PurePath, to force a specific type of path
        pass pathlib.PureWindowsPath or pathlib.PurePosixPath
    :returns: the pathlib.PurePath object
    :rtype: pathlib.PurePath
    """
    windows_path = isinstance(path_class(),pathlib.PureWindowsPath)
    file_uri_parsed = urllib.parse.urlparse(file_uri)
    file_uri_path_unquoted = urllib.parse.unquote(file_uri_parsed.path)
    if windows_path and file_uri_path_unquoted.startswith("/"):
        result = path_class(file_uri_path_unquoted[1:])
    else:
        result = path_class(file_uri_path_unquoted)
    if result.is_absolute() == False:
        raise ValueError("Invalid file uri  : resulting path  not absolute".format(
            file_uri, result))
    return result

使用示例(在linux上运行):

>>> file_uri_to_path("file:///etc/hosts")
PurePosixPath('/etc/hosts')

>>> file_uri_to_path("file:///etc/hosts", pathlib.PurePosixPath)
PurePosixPath('/etc/hosts')

>>> file_uri_to_path("file:///C:/Program Files/Steam/", pathlib.PureWindowsPath)
PureWindowsPath('C:/Program Files/Steam')

>>> file_uri_to_path("file:/proc/cpuinfo", pathlib.PurePosixPath)
PurePosixPath('/proc/cpuinfo')

>>> file_uri_to_path("file:c:/system32/etc/hosts", pathlib.PureWindowsPath)
PureWindowsPath('c:/system32/etc/hosts')

此函数适用于 windows 和 posix 文件 URI,它将处理没有权限部分的文件 URI。但是,它不会验证 URI 的权限,因此不会兑现:

IETF RFC 8089: The "file" URI Scheme / 2. Syntax

“主机”是其所在系统的完全限定域名 该文件是可访问的。这允许另一个系统上的客户端 知道它无法访问文件系统,或者它可能需要 使用其他一些本地机制来访问文件。

函数的验证(pytest):

import os
import pytest

def validate(file_uri, expected_windows_path, expected_posix_path):
    if expected_windows_path is not None:
        expected_windows_path_object = pathlib.PureWindowsPath(expected_windows_path)
    if expected_posix_path is not None:
        expected_posix_path_object = pathlib.PurePosixPath(expected_posix_path)

    if expected_windows_path is not None:
        if os.name == "nt":
            assert file_uri_to_path(file_uri) == expected_windows_path_object
        assert file_uri_to_path(file_uri, pathlib.PureWindowsPath) == expected_windows_path_object

    if expected_posix_path is not None:
        if os.name != "nt":
            assert file_uri_to_path(file_uri) == expected_posix_path_object
        assert file_uri_to_path(file_uri, pathlib.PurePosixPath) == expected_posix_path_object


def test_some_paths():
    validate(pathlib.PureWindowsPath(r"C:\Windows\System32\Drivers\etc\hosts").as_uri(),
        expected_windows_path=r"C:\Windows\System32\Drivers\etc\hosts",
        expected_posix_path=r"/C:/Windows/System32/Drivers/etc/hosts")

    validate(pathlib.PurePosixPath(r"/C:/Windows/System32/Drivers/etc/hosts").as_uri(),
        expected_windows_path=r"C:\Windows\System32\Drivers\etc\hosts",
        expected_posix_path=r"/C:/Windows/System32/Drivers/etc/hosts")

    validate(pathlib.PureWindowsPath(r"C:\some dir\some file").as_uri(),
        expected_windows_path=r"C:\some dir\some file",
        expected_posix_path=r"/C:/some dir/some file")

    validate(pathlib.PurePosixPath(r"/C:/some dir/some file").as_uri(),
        expected_windows_path=r"C:\some dir\some file",
        expected_posix_path=r"/C:/some dir/some file")

def test_invalid_url():
    with pytest.raises(ValueError) as excinfo:
        validate(r"file://C:/test/doc.txt",
            expected_windows_path=r"test\doc.txt",
            expected_posix_path=r"/test/doc.txt")
        assert "is not absolute" in str(excinfo.value)

def test_escaped():
    validate(r"file:///home/user/some%20file.txt",
        expected_windows_path=None,
        expected_posix_path=r"/home/user/some file.txt")
    validate(r"file:///C:/some%20dir/some%20file.txt",
        expected_windows_path="C:\some dir\some file.txt",
        expected_posix_path=r"/C:/some dir/some file.txt")

def test_no_authority():
    validate(r"file:c:/path/to/file",
        expected_windows_path=r"c:\path\to\file",
        expected_posix_path=None)
    validate(r"file:/path/to/file",
        expected_windows_path=None,
        expected_posix_path=r"/path/to/file")

此贡献在Zero-Clause BSD License (0BSD) 许可下获得许可(除了可能适用的任何其他许可)

允许为任何人使用、复制、修改和/或分发此软件 特此授予有偿或无偿的目的。

本软件按“原样”提供,作者否认所有保证 关于本软件,包括所有默示保证 适销性和适应性。在任何情况下,作者概不负责 任何特殊、直接、间接或后果性损害或任何损害 因使用、数据或利润损失而导致的任何情况,无论是在 因合同、疏忽或其他侵权行为而引起的诉讼 或与本软件的使用或性能有关。


在法律允许的范围内,Iwan Aucamp 已放弃此 stackexchange 贡献的所有版权和相关或邻接权。本作品发表于:挪威。

【讨论】:

【参考方案5】:

@colton7909 的解决方案大部分是正确的,并帮助我得到了这个答案,但在 Python 3 中存在一些导入错误。我认为这是处理 URL 的 'file://' 部分的更好方法,而不是简单地砍掉前 7 个字符。所以我觉得这是使用标准库最惯用的方法:

import urllib.parse
url_data = urllib.parse.urlparse('file:///home/user/some%20file.txt')
path = urllib.parse.unquote(url_data.path)

这个例子应该产生字符串'/home/user/some file.txt'

【讨论】:

以上是关于有没有一种方便的方法可以将文件 uri 映射到 os.path?的主要内容,如果未能解决你的问题,请参考以下文章

有没有一种直接的方法可以从文件 Uri 中获取父目录的 Uri?

将URI参数映射到参数对象的属性?

NIOMappedByteBuffer-内存映射文件 I/O

是否可以将完整的 Uris 映射到不同的服务?

有没有一种简单的方法可以将纹理映射到 python 中的不同“UV”系统?

使用实体框架 Fluent Api 映射 System.Uri