当尝试以“w”模式打开隐藏文件时,出现IOError: [Errno 13]权限被拒绝的错误。

30

我想替换一个隐藏文件的内容,因此我尝试以 w 模式打开它,以便它被擦除/截断:

>>> import os
>>> ini_path = '.picasa.ini'
>>> os.path.exists(ini_path)
True
>>> os.access(ini_path, os.W_OK)
True
>>> ini_handle = open(ini_path, 'w')

但是这导致了一条回溯:

IOError: [Errno 13] Permission denied: '.picasa.ini'

然而,我使用r+模式成功地达到了预期结果:

>>> ini_handle = open(ini_path, 'r+')
>>> ini_handle.truncate()
>>> ini_handle.write(ini_new)
>>> ini_handle.close()

问: wr+模式有什么区别,以至于一个会提示“权限拒绝”,而另一个可以正常工作?

更新: 我使用的是Python 2.6.6,在Windows7 x64上运行,目标文件已设置为隐藏属性。当我尝试关闭隐藏属性时,w模式成功执行。但当我重新打开它时,它再次失败。

问: 为什么w模式在隐藏文件上失败?这是已知的行为吗?

3个回答

41
这只是 Win32 API 的工作原理。在底层,Python 的 open 函数调用 CreateFile 函数,如果失败,则将 Windows 错误代码转换为 Python 的 IOErrorr+ 打开模式对应于 dwAccessModeGENERIC_READ|GENERIC_WRITEdwCreationDispositionOPEN_EXISTINGw 打开模式对应于 dwAccessModeGENERIC_WRITEdwCreationDispositionCREATE_ALWAYS
如果您仔细阅读 CreateFile 文档中的注释,它会说:
如果指定了 CREATE_ALWAYSFILE_ATTRIBUTE_NORMAL,则如果文件存在且具有 FILE_ATTRIBUTE_HIDDENFILE_ATTRIBUTE_SYSTEM 属性,则 CreateFile 失败并将最后一个错误设置为 ERROR_ACCESS_DENIED。为避免错误,请指定与现有文件相同的属性。
因此,如果您直接从 C 代码调用 CreateFile,解决方案是在 dwFlagsAndAttributes 参数中添加 FILE_ATTRIBUTE_HIDDEN (而不仅仅是 FILE_ATTRIBUTE_NORMAL)。但是,由于 Python API 中没有选项告诉它传递该标志,因此您只能通过使用不同的打开模式或使文件非隐藏来解决它。

+1 为 Win32 API 文档提供链接。你的解释正是我所寻找的。我自己只能到达 Python 的 open 函数的实现。 - zedex
@MrGamgee:是的,Python调用了_wfopen函数,该函数是Microsoft C运行时库(CRT)的一部分。如果您安装了Visual Studio,则可以查看CRT源代码,通常在类似C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\crt\src的位置。 _wfopen的实现最终调用CreateFile(经过几个中间函数调用)。 - Adam Rosenfield
@Adam,你如何追踪调用了哪个底层的C函数? - Minh Tran
我简直不敢相信这个问题没有更多的关注。谷歌太感谢了!我为此拼命努力了一个多小时,因为我无法修改或写入一个隐藏文件,所以一直认为是文件权限的问题。在我多年的编程经验中,我从未遇到过这种情况。根据谷歌搜索结果,似乎这是唯一一个讨论这种行为的地方。 - hedgehog90

5

以下是详细的区别:

“r” 打开文本文件以进行读取。流被定位在文件的开头。 “r +” 为读取和写入而打开。流被定位在文件的开头。 “w” 截断文件为零长度或创建文本文件以进行写入。流被定位在文件的开头。 “w +” 用于读取和写入。如果该文件不存在,则创建该文件,否则将其截断。流被定位在文件的开头。 “a” 用于写入。如果该文件不存在,则创建该文件。流被定位在文件的末尾。随后对文件的写入将始终结束在文件的当前末尾,而不考虑任何干预fseek(3)或类似操作。 “a +” 用于读取和写入。如果该文件不存在,则创建该文件。流被定位在文件的末尾。随后对文件的写入将始终结束在文件的当前末尾,而不考虑任何干预fseek(3)或类似操作。

从Python文档 - http://docs.python.org/2/tutorial/inputoutput.html#reading-and-writing-files:-

在Windows上,附加到模式的“b”表示以二进制模式打开文件,因此还有像'rb'、'wb'和'r+b'这样的模式。Python在Windows上区分文本文件和二进制文件;读取或写入数据时,文本文件中的行尾字符会自动略微改变。这种对文件数据的幕后修改对于ASCII文本文件来说是可以接受的,但对于像JPEG或EXE文件中的二进制数据来说会破坏它们。在读取和写入此类文件时一定要非常小心地使用二进制模式。在Unix上,附加'b'到模式不会有影响,因此您可以将其用于所有二进制文件,实现跨平台。

因此,如果您使用w模式,则实际上正在尝试创建一个文件,您可能没有权限执行此操作。 r+是适当的选择。

如果您处于一种情况下,还不知道您的 .picasi.ini 文件是否存在,且您的 Windows 用户在该目录中具有文件创建权限,并且您想要添加新信息而不是从文件开头开始(即“追加”),那么 a+ 将是适当的选择。
这与您的文件是否隐藏无关。

我使用了r+模式,该模式允许更新,即既能读取又能写入。 - zedex
你说:“w 截断文件为零长度或创建文本文件进行写入”。但后来你又说:“如果你使用 w 模式,实际上是在尝试创建一个文件。” 到底是哪个? - zedex
如果文件不存在,则创建该文件。如果文件已存在,则将文件截断为零长度,因此您在其上写入的任何内容都从文件开头开始。 - Calvin Cheng
你似乎没有理解我的观点。我检查文件是否存在以及是否具有写入权限的原因仅仅是为了排除那些阅读问题的人可能遇到的 IOError 的可能来源,而不是因为我不知道文件是否存在! - zedex
3
“它与您的文件是否隐藏无关。” 请考虑以下事实: (1) 设置隐藏属性。 (2) w模式失败。 (3) 取消隐藏属性。 (4) w模式成功。 这是可重复的。 我刚刚用“ini”和“jpg”文件进行了检查。 - zedex
这个答案自信地是错误的,我希望您能将其删除。 - oblio

3

感谢这个帖子,我今天也遇到了同样的问题。我的解决办法如下。适用于Python 3.7。

import os

GuiPanelDefaultsFileName = 'panelDefaults.json'
GuiPanelValues = {
    '-FileName-'      : os.getcwd() + '\\_AcMovement.xlsx',
    '-DraftEmail-'    : True,
    '-MonthComboBox-' : 'Jun',
    '-YearComboBox-'  : '2020'
}

# Unhide the file via OS
if os.path.isfile(GuiPanelDefaultsFileName):
    os.system(f'attrib -h {GuiPanelDefaultsFileName}')

# Write dict values to json
with open(GuiPanelDefaultsFileName, 'w') as fp:
    json.dump(GuiPanelValues, fp, indent=4)

# Make it hidden again
os.system(f'attrib +h {GuiPanelDefaultsFileName}')

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