Python 中 file.read() 的多字节请求 EOF

10 投票
2 回答
15469 浏览
提问于 2025-04-16 08:27

Python文档中关于 file.read() 的说明提到,当遇到文件结束(EOF)时,会返回一个空字符串。文档还进一步说明:

需要注意的是,这个方法可能会多次调用底层的C语言函数fread(),以尽量获取接近请求字节数的内容。另外,当处于非阻塞模式时,即使没有给出字节数参数,返回的数据量也可能少于请求的数量。

我相信Guido已经非常明确地表达了他对不添加f.eof()的看法 非常清楚,所以我们需要遵循Python的方式!

不过,对我来说不太清楚的是,如果从读取中得到的字节数少于请求的数量,但仍然得到了数据,这是否可以确定你已经到达了文件结束(EOF)。

也就是说:

with open(filename,'rb') as f:
    while True:
        s=f.read(size)
        l=len(s) 
        if l==0: 
            break     # it is clear that this is EOF...
        if l<size:
            break      # ? Is receiving less than the request EOF???

如果在调用 file.read(size) 时收到的字节数少于请求的数量,直接 break 是否会导致潜在错误?

2 个回答

0

这是我C语言编译器文档中关于fread()函数的说明:

size_t fread( 
   void *buffer,
   size_t size,
   size_t count,
   FILE *stream 
);

fread函数返回实际读取的完整项目数量,如果发生错误或者在达到指定数量之前遇到文件末尾,那么返回的数量可能会少于你要求的数量。

所以,如果得到的数量少于size,这就意味着可能发生了错误,或者已经到达了文件的末尾——在这种情况下,break退出循环是正确的做法。

23

你可能没有意识到,Python和C语言是完全不同的。

首先,来回顾一下:

  • st=f.read() 会读取文件直到结束,或者如果以二进制方式打开,则读取到最后一个字节;
  • st=f.read(n) 尝试读取 n 个字节,但最多只会读取 n 个字节;
  • st=f.readline() 一次读取一行,行的结束标志是 '\n' 或文件结束;
  • st=f.readlines() 使用 readline() 来读取文件中的所有行,并返回一个包含这些行的列表。

如果文件读取方法到达了文件结束(EOF),它会返回 ''。在其他类似文件的操作中,比如 StringIO、socket.makefile 等,也会使用相同的 EOF 测试。从 f.read(n) 返回的字节数少于 n 并不一定意味着到达了文件结束!虽然这段代码在99.99%的情况下可能有效,但在它不工作的情况下,找到问题会非常让人沮丧。而且,这种写法在 Python 中也不太好。这里 n 的唯一作用是限制返回值的大小。

那么,Python 的文件操作方法为什么会返回 少于 n 个字节呢?

  1. 文件结束(EOF)当然是一个常见原因;
  2. 网络套接字在读取时可能会超时,但仍然保持打开状态;
  3. 恰好 n 个字节可能会导致逻辑多字节字符之间的断裂(比如文本模式下的 \r\n,还有我认为的 Unicode 中的多字节字符)或一些你不知道的底层数据结构;
  4. 文件处于非阻塞模式,另一个进程开始访问该文件;
  5. 暂时无法访问文件;
  6. 文件、磁盘、网络等出现了潜在的错误情况,可能是暂时的;
  7. 程序收到了一个信号,但信号处理程序忽略了它。

我会这样重写你的代码:

with open(filename,'rb') as f:
    while True:
        s=f.read(max_size)
        if not s: break

        # process the data in s...

或者,写一个 生成器

def blocks(infile, bufsize=1024):
    while True:
        try:
            data=infile.read(bufsize)
            if data:
                yield data
            else:
                break
        except IOError as (errno, strerror):
            print "I/O error({0}): {1}".format(errno, strerror)
            break

f=open('somefile','rb')

for block in blocks(f,2**16):
    # process a block that COULD be up to 65,536 bytes long

撰写回答