【问题标题】:file.tell() inconsistencyfile.tell() 不一致
【发布时间】:2013-01-03 18:39:14
【问题描述】:

当您以这种方式迭代文件时,是否有人碰巧知道为什么:

输入:

f = open('test.txt', 'r')
for line in f:
    print "f.tell(): ",f.tell()

输出:

f.tell(): 8192
f.tell(): 8192
f.tell(): 8192
f.tell(): 8192

我总是从 tell() 获得错误的文件索引,但是,如果我使用 readline,我会为 tell() 获得适当的索引:

输入:

f = open('test.txt', 'r')
while True:
    line = f.readline()
    if (line == ''):
        break
    print "f.tell(): ",f.tell()

输出:

f.tell(): 103
f.tell(): 107
f.tell(): 115
f.tell(): 124

我正在运行 python 2.7.1 BTW。

【问题讨论】:

  • 只是涵盖了所需的问题:您确定您没有到达第一个示例中的文件末尾吗?

标签: python python-2.7 file buffering


【解决方案1】:

将打开的文件用作迭代器会使用预读缓冲区来提高效率。因此,当您遍历行时,文件指针会在文件中大步前进。

来自File Objects 文档:

为了使 for 循环成为循环文件行的最有效方法(一种非常常见的操作),next() 方法使用隐藏的预读缓冲区。作为使用预读缓冲区的结果,将next() 与其他文件方法(如readline())组合起来无法正常工作。但是,使用seek() 将文件重新定位到绝对位置会刷新预读缓冲区。

如果您需要依赖.tell(),请不要将文件对象用作迭代器。您可以将.readline() 改为迭代器(以牺牲一些性能为代价):

for line in iter(f.readline, ''):
    print f.tell()

这使用iter() function sentinel 参数将任何可调用对象转换为迭代器。

【讨论】:

  • 只是补充一点,如果你知道文件从哪里开始(例如从 0 或以前的seek(),你可以手动跟踪文件位置而不是使用tell() . 只需将计数器增加您从next() 读取的每一行的长度即可。现在您有了一个“正确”的文件位置,并且预读带来了性能提升。
  • @TomDalton:您不能在 Windows 平台上执行此操作,因为会转换行分隔符。在那里读取一行会为磁盘上的每 x + 1 个字节提供 x 个字符。这在使用 io.open() 时也不起作用,其中磁盘上的多字节字符被解码为一个 unicode 代码点。幸运的是 io.open() 文件对象不需要此答案中提供的解决方法。
  • @MartijnPieters。无耻的自我广告:stackoverflow.com/q/48055409/2988730。这与 tell 在 Py3 迭代中被完全禁用的原因相同吗?
  • @MadPhysicist:不,Python 3 的原因不同; TextIO* 包装器依赖于几层缓冲和支持告诉会产生严重的性能影响。
【解决方案2】:

答案就在 Python 2.7 源代码 (fileobject.c) 的以下部分:

#define READAHEAD_BUFSIZE 8192

static PyObject *
file_iternext(PyFileObject *f)
{
    PyStringObject* l;

    if (f->f_fp == NULL)
        return err_closed();
    if (!f->readable)
        return err_mode("reading");

    l = readahead_get_line_skip(f, 0, READAHEAD_BUFSIZE);
    if (l == NULL || PyString_GET_SIZE(l) == 0) {
        Py_XDECREF(l);
        return NULL;
    }
    return (PyObject *)l;
}

如您所见,file 的迭代器接口以 8KB 的块为单位读取文件。这就解释了为什么f.tell() 的行为方式如此。

文档suggests 是出于性能原因(并且不保证任何特定大小的预读缓冲区)。

【讨论】:

【解决方案3】:

我遇到了同样的预读缓冲区问题,并使用Martijn's suggestion 解决了它。

我已经将我的解决方案推广给其他想要做这些事情的人:

https://github.com/loisaidasam/csv-position-reader

CSV 解析愉快!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-24
    • 1970-01-01
    • 2014-06-14
    • 2017-01-16
    相关资源
    最近更新 更多