【问题标题】:PyPy file append modePyPy 文件追加模式
【发布时间】:2014-04-18 14:24:53
【问题描述】:

我有这样的代码:

f1 = open('file1', 'a')
f2 = open('file1', 'a')

f1.write('Test line 1\n')
f2.write('Test line 2\n')
f1.write('Test line 3\n')
f2.write('Test line 4\n')

当使用标准 Python 2.7 解释器运行此代码时,该文件包含预期的四行。但是,当我在 PyPy 下运行这段代码时,该文件只包含两行。

有人能解释一下 Python 和 PyPy 在以追加模式处理文件方面的区别吗?

已更新:PyPy 2.3 中不存在该问题。

【问题讨论】:

  • 为什么你会用两个不同的句柄打开同一个文件?
  • 我在旧代码中有这样的代码,很难更改。使用标准 Python 时,此代码有效,但不适用于 PyPy。
  • 这是一个缓冲和刷新文件内容的问题。在 PyPy 上,文件的提交是延迟的,因此提交文件的最后一个句柄会替换它的全部内容。

标签: python pypy


【解决方案1】:

不同行为的原因是文件I/O操作的不同实现。

CPython implements 它是 fopenfreadfwrite 之上的文件 I/O 函数,来自 stdio.h,并且正在处理 FILE * 流。

同时 PyPy implements 是 POSIX openwriteread 函数之上的文件 I/O,并且正在使用 int 文件描述符。

比较这两个 C 程序:

#include <stdio.h>

int main() {
    FILE *a = fopen("file1", "a");
    FILE *b = fopen("file1", "a");

    fwrite("Test line 1\n", 12, 1, a);
    fflush(a);
    fwrite("Test line 2\n", 12, 1, b);
    fflush(b);
    fwrite("Test line 3\n", 12, 1, a);
    fflush(a);
    fwrite("Test line 4\n", 12, 1, b);

    fclose(a);
    fclose(b);

    return 0;
}

#include <fcntl.h>
#include <unistd.h>

int main() {
    int a = open("file1", O_CREAT | O_WRONLY | O_APPEND);
    int b = open("file1", O_CREAT | O_WRONLY | O_APPEND);

    write(a, "Test line 1\n", 12);
    write(b, "Test line 2\n", 12);
    write(a, "Test line 3\n", 12);
    write(b, "Test line 4\n", 12);

    close(a);
    close(b);

    return 0;
}

有关openfopen 之间区别的更多信息,您可以在question 的答案中找到。

更新

在进一步检查 PyPy 代码库之后,在我看来,出于某种原因,doesn't use O_APPEND flag,但 O_WRONLY | O_CREAT 用于“a”模式。因此,正如 J.F. Sebastian 在 another answer 中提到的那样,在 PyPy 中,您需要在每次 write 调用后将 seek 放到文件末尾,这才是真正的原因。我想应该在 PyPy bugtracker 上创建一个错误,因为O_APPEND 标志在 Windows 和 Unix 上都可用。所以,PyPy 现在看起来像这样:

#include <fcntl.h>
#include <unistd.h>

int main() {
    int a = open("file1", O_CREAT | O_WRONLY);
    int b = open("file1", O_CREAT | O_WRONLY);

    write(a, "Test line 1\n", 12);
    write(b, "Test line 2\n", 12);
    write(a, "Test line 3\n", 12);
    write(b, "Test line 4\n", 12);

    close(a);
    close(b);

    return 0;
}

如果没有 O_APPEND 标志,它应该重现 PyPy 行为。

【讨论】:

  • 你不得不怀疑 PyPy 是否遵守规范,或者在这种情况下规范是否不够具体。
  • @wheaties 我认为没有这样的规范,它是特定于实现的。 File Objects 的 CPython 文档是在 stdio.h 之上实现的。同时它在 POSIX 函数之上具有 io 模块 implemented
  • 注意:CPython 3 还在 POSIX openwriteread 之上实现了 I/O。它可以作为 Python 2.7 中的 io 模块访问
  • 在我的机器上,尽管 I/O 实现不同,但您的 C 程序、Python 2、Python 3、Jython 都会产生相同的输出。只有 Pypy 不同
  • @J.F.Sebastian 我的系统出现问题,导致对运行我的 C 代码的解释不正确。在我看来,我现在在实现中发现了一个真正的错误,并添加了另一个 C 代码示例来重现它。
【解决方案2】:

On POSIX systems:

O_APPEND
如果设置,文件偏移量应设置为文件末尾 在每次写入之前。

这意味着如果一个文件以“追加”模式打开,那么当它的缓冲区被刷新时;内容应放在文件末尾。

Python 2、Python 3、Jython 在我的机器上尊重这一点。在您的情况下,内容小于文件缓冲区,因此您会在磁盘上的结果中看到来自一个文件的所有写入,然后是来自另一个文件的所有写入。

如果文件是行缓冲的更容易理解:

from __future__ import with_statement

filename = 'file1'
with open(filename, 'wb', 0) as file:
    pass # truncate the file

f1 = open(filename, 'a', 1)
f2 = open(filename, 'a', 1)

f1.write('f1 1\n')
f2.write('f2 aa\n')
f1.write('f1 222\n')
f2.write('f2 bbbb\n')
f1.write('f1 333\n')
f2.write('f2 cc\n')

输出

f1 1
f2 aa
f1 222
f2 bbbb
f1 333
f2 cc

Python 文档不强制要求此类行为。 It just mentions:

..'a' 用于附加(在某些 Unix 系统上,这意味着 所有写入都附加到 该文件与当前的搜索位置无关)强调是我的

Pypy 在非缓冲和行缓冲模式下产生以下输出:

f2 aaff2 bbbf1f2 cc

手动将文件位置移动到末尾修复pypy输出:

from __future__ import with_statement
import os

filename = 'file1'
with open(filename, 'wb', 0) as file:
    pass # truncate the file

f1 = open(filename, 'a', 1)
f2 = open(filename, 'a', 1)

f1.write('f1 1\n')
f2.seek(0, os.SEEK_END)
f2.write('f2 aa\n')
f1.seek(0, os.SEEK_END)
f1.write('f1 222\n')
f2.seek(0, os.SEEK_END)
f2.write('f2 bbbb\n')
f1.seek(0, os.SEEK_END)
f1.write('f1 333\n')
f2.seek(0, os.SEEK_END)
f2.write('f2 cc\n')

如果文件是完全缓冲的,则在每个 .write() 之后添加 .flush()

一次使用多个文件对象写入同一个文件可能不是一个好主意。

【讨论】:

    猜你喜欢
    • 2010-11-07
    • 2011-06-14
    • 1970-01-01
    • 1970-01-01
    • 2018-12-08
    • 2016-10-21
    • 1970-01-01
    • 2022-12-07
    • 1970-01-01
    相关资源
    最近更新 更多