【问题标题】:Why isn't truncate defaulting properly to the current position for files?为什么截断不能正确默认为文件的当前位置?
【发布时间】:2016-01-19 14:26:08
【问题描述】:

在对another question 的回答中,观察到一种奇怪的行为,特定于 Python 3。documentation for the truncate command 状态(强调我的):

将流的大小调整为以字节为单位的给定大小(如果未指定大小,则为当前位置)。当前的流位置没有改变。这种调整大小可以扩展或减小当前文件大小。在扩展的情况下,新文件区域的内容取决于平台(在大多数系统上,附加字节是零填充的)。 返回新的文件大小。

不过……

>>> open('temp.txt', 'w').write('ABCDE\nFGHIJ\nKLMNO\nPQRST\nUVWXY\nZ\n')
32
>>> f = open('temp.txt', 'r+')
>>> f.readline()
'ABCDE\n'
>>> f.tell()
6                   # As expected, current position is 6 after the readline
>>> f.truncate()
32                  # ?!

不是在当前位置 (6) 截断,而是在文件末尾截断(即根本不截断)。这是通过检查磁盘上的文件来验证的。

此过程在 Python 2 中按预期工作(文件被截断为 6 个字节),并且在 Python 3 中使用 StringIO 而不是文件。为什么 Python 3 中的文件不能按预期工作?这是一个错误吗?

(编辑:如果在truncate 之前给出明确的f.seek(6),它也可以正常工作。)

【问题讨论】:

  • 如果您在打开第二个文件对象之前close 第一个文件对象,是否还会出现这种情况?
  • @Kevin 是的。 (评论填充)
  • 这种行为是否与上下文管理with ... as f 以及您使用的是哪个特定版本的python 3 相同?你也可以先做f.seek(6),然后再做f.truncate()吗?我不知道这个错误,并且想要提到的事情,但我正在旅途中。 (我知道with ... 只是围绕f = open(),但同样,在所有理论实现中都可以找到奇怪的故障和失误)
  • @Torxed 刚刚编辑添加了一个明确的seektruncate 之前确实可以正常工作。我试过 3.4.1 和 3.4.3。它已被其他人复制,但我无法说出他们的具体版本。
  • @glibdud 不确定我是否应该为此写一个答案。但我很高兴.truncate() 没有通过事先隐含地调用它自己的搜索来破坏.seek(x)

标签: python python-3.x file-io


【解决方案1】:
>>> open('temp.txt', 'w').write('ABCDE\nFGHIJ\nKLMNO\nPQRST\nUVWXY\nZ\n')
32
>>> f = open('temp.txt', 'r+')
>>> f.readline()
'ABCDE\n'
>>> f.seek(6) 
>>> f.truncate()

如果没有其他问题,这可以解决问题,至于为什么会发生这种情况我不知道,但如果还没有报告这个上游,那将是一件好事。

这些是我能找到的与 Python3 和 Python2 之间的 truncate() 函数的唯一结构差异(显然,截断函数本身内的相关函数调用除外):

33,34c33,34
<             except AttributeError as err:
<                 raise TypeError("an integer is required") from err
---
>             except AttributeError:
>                 raise TypeError("an integer is required")
54c54
<         """Truncate size to pos, where pos is an int."""
---
>         """Truncate size to pos."""

我相信有人会在这个问题上打我的耳光,但我认为这与flush() 调用以及调用flush 后缓冲区的处理方式更相关。几乎就像在刷新所有 I/O 后它没有重置到之前的位置一样。这是一个疯狂的假设,目前还没有技术支持,但这是我的第一个猜测。

查看flush()的情况,这里是两者的唯一区别,其中Python2执行了Python3没有的以下操作(甚至缺少它的源代码):

def _flush_unlocked(self):
    if self.closed:
        raise ValueError("flush of closed file")
    while self._write_buf:
        try:
            n = self.raw.write(self._write_buf)
        except BlockingIOError:
            raise RuntimeError("self.raw should implement RawIOBase: it "
                               "should not raise BlockingIOError")
        except IOError as e:
            if e.errno != EINTR:
                raise
            continue
        if n is None:
            raise BlockingIOError(
                errno.EAGAIN,
                "write could not complete without blocking", 0)
        if n > len(self._write_buf) or n < 0:
            raise IOError("write() returned incorrect number of bytes")
        del self._write_buf[:n]

这是BufferedWriter 的函数,似乎在此 I/O 操作中使用。
现在我约会迟到了,所以必须冲刺,看看你们在此期间发现了什么会很有趣!

【讨论】:

  • 创建Issue 26158,如果您想在有空时添加您的详细信息。
  • @glibdud 我只想告诉你,我仍然关注这个问题,并且我还没有添加任何额外的反馈,因为你们已经清除了大部分内容并且你指出了这个 SO 问题我认为这是足够的信息。我会密切关注这个问题,直到它得到解决,因为它对我参与的许多项目也很感兴趣,呵呵。
【解决方案2】:

为此,我在 Python 问题跟踪器上打开了an issue,答案似乎与缓冲有关:

>>> open('temp.txt', 'w').write('ABCDE\nFGHIJ\nKLMNO\nPQRST\nUVWXY\nZ\n')
32
>>> f = open('temp.txt', 'r+')
>>> f.readline()
'ABCDE\n'
>>> f.tell()
6
>>> f.buffer.tell()
32

出于某种原因,truncate 使用缓冲区位置,而不是高级流位置。这实际上不仅限于truncate,还会产生其他意想不到的结果,例如:

>>> open('temp.txt', 'w').write('ABCDE\nFGHIJ\nKLMNO\nPQRST\nUVWXY\nZ\n')
32
>>> f = open('temp.txt', 'r+')
>>> f.readline()
'ABCDE\n'
>>> f.write('test')
4
>>> f.close()
>>> open('temp.txt').read()
'ABCDE\nFGHIJ\nKLMNO\nPQRST\nUVWXY\nZ\ntest'

一位开发人员has stated 说这里有一个问题,尽管我不能从他的陈述中完全确定问题是什么。不过,它似乎被标记为要修补。

【讨论】:

  • 奇怪的是,开发人员甚至在 _pyio.py 的截断函数中添加了“我们应该在此处执行 .seek()”
  • @Torxed 他提供了一个使用 UTF-7 的示例来展示尝试在文本模式文件中定位的危险。这是一个公平的观点,但如果他们至少有特殊情况的 UTF8、ASCII 和任何其他更直接的编码能够按预期工作,那就太好了。
  • 我完全同意你的看法。
猜你喜欢
  • 1970-01-01
  • 2016-03-16
  • 1970-01-01
  • 1970-01-01
  • 2021-11-24
  • 2023-03-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多