Python : 使用 brackets/long 路径名读取文件名时出现问题

Python : Problem reading filename with brackets/long path name

我正在尝试用 pandas 读取 excel 文件。

df=pd.read_excel('abcd (xyz-9) Interim Report 01-03-18.xlsx')

这给我找不到文件的错误。如果我删除括号并将文件重命名为 'abcd Interim Report 01-03-18.xlsx',那么它工作正常。

我尝试用 shutil 重命名,但它给了我同样的错误

shutil.copyfile('abcd (xyz-9) Interim Report 01-03-18.xlsx','test.xlsx')

我试过了

1. pd.read_excel('abcd ^(xyz-9) Interim Report 01-03-18.xlsx')
2. pd.read_excel('abcd \(xyz-9\) Interim Report 01-03-18.xlsx')

编辑:

该文件似乎在本地驱动器上有效,但在网络驱动器上无效,即使我将 cwd 更改为文件位置也是如此。

关于使用 glob 和 os.path.exists:

for i in range(0,1):
    for filename in glob.glob(fpath+"\"+ldir[i]+"\"+"*Interim*.xlsx"):
        print(filename)
        print(os.path.exists(filename))
\Africa-me.xxx.com\Africa-me\xxx\xxx\xxx\xxx xxx xxx, xxx and xxxx xxx xxx xxx xx xx & xx 2018 xx xx xxx\\AAA-61\abcd (xyz-9) Interim Report 01-03-18.xlsx
False

\Africa-me.xxx.com\Africa-me\xxx\xxx\xxx\xxx xxx xxx, xxx and xxxx xxx xxx xxx xx xx & xx 2018 xx xx xxx\\AAA-61\abcd Interim Report 01-03-18.xlsx
True

关于使用 glob 和 os.stat:

import ctypes

for i in range(0,1):
    for filename in glob.glob(fpath+"\"+ldir[i]+"\"+"*Interim*.xlsx"):
        print(filename)
        try:
            print(os.stat(filename))
        except OSError as e: 
            ntstatus = ctypes.windll.ntdll.RtlGetLastNtStatus()
            print('winerror:', e.winerror) 
            print('ntstatus:', hex(ntstatus & (2**32-1)))
\Africa-me.xxx.com\Africa-me\xxx\xxx\xxx\xxx xxx xxx, xxx and xxxx xxx xxx xxx xx xx & xx 2018 xx xx xxx\\AAA-61\abcd (xyz-9) Interim Report 01-03-18.xlsx
winerror: 3
ntstatus: 0x80000006

\Africa-me.xxx.com\Africa-me\xxx\xxx\xxx\xxx xxx xxx, xxx and xxxx xxx xxx xxx xx xx & xx 2018 xx xx xxx\\AAA-61\abcd Interim Report 01-03-18.xlsx
os.stat_result(st_mode=33206, st_ino=15624813576354602, st_dev=3657573641, st_nlink=1, st_uid=0, st_gid=0, st_size=726670, st_atime=1563172745, st_mtime=1523347973, st_ctime=1563170560) 

每当我处理文件时,我总是尝试使用 python 的 pathlib 模块。文件名

中的初始 a 似乎有错误
import pandas as pd                                                        
import pathlib                                                             
path_to_file = pathlib.Path("g:\Python\abcd (xyz-9) Interim Report 01-03-18.xlsx")
df = pd.read_exces(path_to_file)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 'pandas' has no attribute 'read_exces'
df = pd.read_excel(path_to_file)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Program Files\Python37\lib\site-packages\pandas\util\_decorators.py", line 188, in wrapper
    return func(*args, **kwargs)
  File "C:\Program Files\Python37\lib\site-packages\pandas\util\_decorators.py", line 188, in wrapper
    return func(*args, **kwargs)
  File "C:\Program Files\Python37\lib\site-packages\pandas\io\excel.py", line 350, in read_excel
    io = ExcelFile(io, engine=engine)
  File "C:\Program Files\Python37\lib\site-packages\pandas\io\excel.py", line 653, in __init__
    self._reader = self._engines[engine](self._io)
  File "C:\Program Files\Python37\lib\site-packages\pandas\io\excel.py", line 424, in __init__
    self.book = xlrd.open_workbook(filepath_or_buffer)
  File "C:\Program Files\Python37\lib\site-packages\xlrd\__init__.py", line 111, in open_workbook
    with open(filename, "rb") as f:
OSError: [Errno 22] Invalid argument: 'g:\Python\x07bcd (xyz-9) Interim Report 01-03-18.xlsx'

似乎因为前面的反斜杠,最初的 a 在某些时候被解释为控制符号 \x07 U+0007 : ALERT [BEL].

这就是为什么在定义路径时需要使用原始字符串,如Dawid suggested

path_to_file = pathlib.Path(r"g:\Python\abcd (xyz-9) Interim Report 01-03-18.xlsx")

os.stat 测试表明,访问带括号的路径失败并显示 ERROR_PATH_NOT_FOUND (3),这是由于缺少路径组件或路径太长所致。我们知道找到最终路径组件不是问题,因为在这种情况下我们预计错误为 ERROR_FILE_NOT_FOUND (2)。我们知道这不是保留字符的问题,因为在这种情况下我们预计错误是 ERROR_INVALID_NAME (123)。此外,在这两种情况下,Windows API 都必须进行 NT 系统调用,但我们看到最后的 NT 状态是 STATUS_NO_MORE_FILES (0x80000006),它来自os.listdir 来电 glob.glob。因此,问题很可能是路径太长。

为了简洁或隐私,问题似乎将一些路径组件名称缩短为 "xxx"。可能如果扩展到真实姓名,我们会看到带有“(xyz-9)”的路径至少有 260 个字符,这比 DOS 路径的最大允许长度多一个,MAX_PATH - 1 (259) 个字符。

我们可以通过将长路径转换为扩展路径来访问长路径,扩展路径是以“\\?\” device-path 前缀或“\\?\UNC\”开头的 Unicode 字符串对于 UNC 路径。首先,由于 Windows 中的字节路径仅限于 MAX_PATH 个字符,我们必须将字节路径解码为 Unicode。接下来我们必须通过 os.path.abspath 规范化和限定路径,因为扩展路径在访问时会绕过规范化。这是将 DOS 路径转换为扩展路径的 extpath 函数:

import os

try:
    from os import fsdecode
except ImportError: # Probably Python 2.x
    import sys
    def fsdecode(filename):
        if isinstance(filename, type(u'')):
            return filename
        elif isinstance(filename, bytes):
            return filename.decode(sys.getfilesystemencoding(), 'strict')
        raise TypeError('expected string, not {}'.format(
                type(filename).__name__))

def extpath(path):
    path = os.path.abspath(fsdecode(path))
    if not path.startswith(u'\\?\'):
        if path.startswith(u'\\.\'):
            path = u'\\?\' + path[4:]
        elif path.startswith(u'\\'):
            path = u'\\?\UNC\' + path[2:]
        else:
            path = u'\\?\' + path
    return path

背景

Windows NT(即自 XP 以来的所有 Windows 版本)的核心是 NTOS 操作系统,它使用 NT 内核。这类似于 16 位 Windows 在 1980 年代和 1990 年代初期在 DOS 上分层的方式。但是 NTOS 比 DOS 更紧密地耦合到 Windows,而且它是一个更强大的 OS(例如支持对称多处理、抢占式多线程、虚拟内存、异步I/O、受保护的对象和具有同时登录和会话的多个用户)。

在某些方面 Windows 仍然保持其 MS-DOS 的根源。特别是,对于磁盘设备和文件系统路径,Windows API 使用 DOS 路径而不是 NT 对象路径。这包括 DOS 驱动器 "A:" 到 "Z:" 和 UNC 路径,例如“\\server\share\path”。 Windows API 标准化 DOS 路径以用反斜杠替换正斜杠;使用进程工作目录或 per-drive 工作目录解析相对路径(即没有根目录或没有驱动器的路径);解决 ”。”和“..”组件;和 trim 尾随 spaces 和最终组件的点。

访问 DOS 路径时,Windows 将其转换为以 WINAPI 设备前缀之一“\\.\”和“ \\?\"(例如 "C:\Windows" -> "\\?\C:\Windows")。对于 drive-letter 路径和相对路径(但不是 UNC 路径),它在最终路径组件中保留一小组 DOS 设备名称(例如 "C:\Temp\con" -> "\\.\ con" 和 "nul" -> "\\.\nul")。对于 UNC 路径,它使用 "UNC" 设备安装点(例如 \\server\share\path -> "\\?\UNC\server\share\path")。在呼叫传递到 NT 域之前,WINAPI 设备前缀(“\\.\”或“\\?\”)被 NTAPI 设备前缀(“\?? \”)。

这些 device-namespace 前缀是 shorthand NT 对象名称中调用者的本地挂载点目录 space(即“\Sessions[=67=]\DosDevices\<调用者的登录会话 ID>”) .本地挂载点目录隐式隐藏全局挂载点目录(即“\Global??”)。例如,"C:\Temp" 变为“\??\C:\Temp”,如果全局 "C:" 未在本地隐藏,则计算结果为“\Global??\C:\Temp”。全局挂载点可以通过 "Global" 对象 link(例如“\\?\Global\C:\Temp”)显式引用,它应该始终可用。

传统上,DOS 路径规范化使用 space 的字符串缓冲区不超过 MAX_PATH (260) 个字符。在 Windows 10 中,如果为系统启用了长路径并且应用程序清单声明它是 long-path 感知的,那么(在大多数情况下)将取消此遗留限制。这 "python[w].exe" Python 3.6+ 中的可执行文件具有此清单设置。

如果不启用长 DOS 路径,大多数文件 API 函数仍然支持长路径。我们只需要使用以“\\?\”前缀开头的 WINAPI 设备路径即可,这称为扩展路径。这类似于以“\\.\”前缀开头的常规设备路径,只是扩展路径在访问时不会被规范化。缺点是我们必须实现自己的路径规范化。在 Python 中,这是由 os.path.abspath 实现的。我们仍然需要手动重写 UNC 路径,但只需将前导“\\”替换为“\\?\UNC\”即可。

请注意,工作目录不支持设备路径,无论是否扩展。如果我们将设备路径设置为工作目录,系统会出现未定义的行为。所以不要将它们与 Python 的 os.chdirsubprocess.Popencwd 参数一起使用。这意味着我们无法通过设置扩展路径来绕过使用长相对路径的限制作为工作目录。在Windows10中,如果为进程启用长DOS路径,工作目录确实支持长路径,但它仍然只支持常规DOS路径(UNC和drive-letter 路径),而不是设备路径。