Python与C的文件操作
程序读写持久化数据要经过操作系统提供的文件接口。Python在open() 之上封装了文件对象,C用标准库 <stdio.h> 的流和FILE * 指针。两套接口解决同一个问题,但打开失败、关闭时机、文本解码和定位方式都不同。本页按“打开、读写、定位、关闭”的顺序逐项对照。模式规则分别见Python文件打开模式和C文件流的打开模式,二者最终都通过系统调用进入内核,系统调用层见 系统调用。
定义
Python的文件对象
open(file, mode="r") 返回一个文件对象,mode中含b时是二进制文件对象,否则是文本文件对象。对象内部保存打开的文件描述符、缓冲区、当前位置和解码器,后续的读取、写入、定位都作用在这个对象上。
f = open("data.txt", "r")text = f.read()f.close()文本模式返回str,二进制模式返回bytes。文本模式的编码由encoding参数决定,省略时使用平台相关的默认编码。
C的流与FILE指针
C把文件抽象成流:一个有序的字节序列,附带缓冲区、当前位置、错误指示器和文件结束标志。FILE是保存这些状态的结构体,程序拿到的始终是FILE * 类型的文件指针。
FILE *fopen(const char *filename, const char *mode);int fclose(FILE *stream);fopen成功时返回文件指针,失败时返回NULL;fclose成功返回0,失败返回EOF。
关闭文件
关闭动作把缓冲区里尚未写出的数据刷到设备,并释放文件描述符。Python的close() 和C的fclose() 都承担这件事,区别在于语言是否替你保证调用:
- Python:用
with open(...) as f:时,离开代码块会自动调用close(),即使块内抛出异常也会执行。手写的f.close()在异常路径上容易被跳过。 - C:没有语言级的自动关闭,必须显式
fclose()。进程正常退出时标准库会刷新并关闭仍打开的流,但依赖这一点会掩盖错误,长期运行的程序还可能耗尽文件描述符。
读写与定位
Python的读取与写入
| 操作 | 行为 |
|---|---|
read(size=-1) | 读取至多size个字符(文本)或字节(二进制),省略时读到文件末尾。 |
readline(size=-1) | 读取一行,返回的字符串保留行尾换行符;最后一行没有换行符时原样返回。 |
readlines(hint=-1) | 一次读完,返回由各行组成的列表。 |
write(s) | 写入字符串或字节,返回写入的字符数;不会自动补换行符。 |
文件对象本身可以迭代,for line in f: 逐行读取,比readlines() 更省内存:后者会把整个文件装进列表。
C的读写
C的stdio按处理单位分成几层,各有对应的函数:
| 层次 | 读 | 写 | 典型用途 |
|---|---|---|---|
| 字符 | fgetc(stream) | fputc(c, stream) | 逐字符处理 |
| 行 | fgets(s, n, stream) | fputs(s, stream) | 按行处理文本,不解析格式 |
| 格式化 | fscanf(stream, format, ...) | fprintf(stream, format, ...) | 字段格式已知的结构化文本 |
| 块 | fread(ptr, size, nmemb, stream) | fwrite(ptr, size, nmemb, stream) | 二进制数据、结构体数组 |
几个容易误读的返回值:
fscanf返回成功赋值的输入项个数,不是读到的字节数。fscanf(fp, "%d %s", &id, name)成功读到两个字段时返回2。fread和fwrite返回成功读写的元素个数,每个元素占size字节。只有返回值等于nmemb才算完整成功,返回0可能表示读到末尾,也可能表示出错。
定位
Python用tell() 取当前位置,用seek(offset, whence=0) 移动,whence取0、1、2表示相对文件开头、当前位置和文件末尾。文本模式下的offset不是可靠的字节偏移,而是编码器给出的位置值;可靠的做法是只使用0、tell() 的返回值和文件末尾。要按字节精确定位,改用二进制模式。
C用ftell(fp) 取位置,用fseek(fp, offset, whence) 移动,whence取SEEK_SET、SEEK_CUR、SEEK_END。rewind(fp) 等价于移到文件开头并清除文件结束标志。fseek成功返回0,失败返回非0。
两种接口都带缓冲,写到文件的数据可能还留在内存里。需要将语言库中的输出缓冲交给底层文件接口时,Python调用flush(),C调用fflush(fp);关闭文件也会尝试刷新缓冲区。刷新库缓冲并不保证数据已经持久写入存储介质。
一次读写操作的顺序
-
打开并检查结果
Python把打开放在
with里,失败时直接抛出异常;C判断fopen是否返回NULL,失败时读取errno再决定退出还是重试。 -
确定起始位置
新打开的文件按模式决定起点。要从头读刚写过的内容,用
seek(0)或rewind回到开头。 -
读写并检查数量
Python按字符或字节计数,短读要自己循环补足;C检查
fread、fwrite的返回值是否等于nmemb,用feof与ferror区分读到末尾和出错。 -
刷新并关闭
需要刷新输出缓冲时先
flush()或fflush(),再关闭文件。Python的with在退出时自动完成关闭,C必须显式fclose()并检查返回值。
两种接口的差异
| 维度 | Python | C |
|---|---|---|
| 打开结果 | 文件对象 | FILE * |
| 打开失败 | 抛出OSError的子类,如FileNotFoundError | 返回NULL,原因记录在errno |
| 关闭 | close() 或with | fclose(),手动调用 |
| 文本处理 | 按encoding解码和编码 | 只做换行转换,不处理字符编码 |
| 读写单位 | 字符、行、整块等高层方法 | 字符、行、格式化、块四层函数 |
| 定位 | tell() / seek() | ftell() / fseek() |
| 错误检查 | 异常 | 返回值、feof()、ferror() |
例子
Python:逐行读取文件
f = open("./Task1.txt", "r")fls = f.readlines()for line in fls: line = line.strip() print(line)f.close()readlines() 一次读出所有行,strip() 去掉两端空白后再打印。这段代码依赖末尾的f.close() 释放文件。
Python:用上下文管理器保证关闭
with open("./Task1.txt", "r") as f: fls = f.readlines() for line in fls: line = line.strip() print(line)逻辑和上例相同,但离开with块时无论是否发生异常都会关闭文件,不需要手写close()。
Python:读取后计算并写回
f = open("./Task1.txt", "r+")fls = f.readlines()for line in fls: line = line.strip() lstr = line.split() if lstr[0] == "3": res = 0 for e in lstr[1:]: res += int(e)f.write("\n4 " + str(res))f.close()文件以 "r+" 打开,可以读也可以写。readlines() 读完后位置已经在文件末尾,所以后面的write是追加,而不是覆盖开头的内容。
C:写入后回读
#include <stdio.h>
int main(void) { FILE *fp = fopen("scores.txt", "w+"); if (fp == NULL) { perror("fopen"); return 1; }
fprintf(fp, "%d %s\n", 1, "Alice"); rewind(fp);
int id; char name[32]; if (fscanf(fp, "%d %31s", &id, name) == 2) { printf("%d %s\n", id, name); }
fclose(fp); return 0;}"w+" 先截断再以读写方式打开;写入后必须rewind把位置移回开头,才能读到刚写的内容。fopen的返回值先判空,fclose放在最后释放流。格式化读入字符串时用 %31s限定宽度,避免超出name的容量。
探索
读操作如何推进文件位置?
逐次读取固定字节并回到开头。
正在准备演示…
练习
open("data.txt")等价于下列哪种写法?参考解答
默认模式是
"r",即只读文本模式,等价于"rt"。文件
data.txt恰好有两行。len(open("data.txt").readlines())的值是 。参考解答
readlines()为每一行返回一个列表元素,两行就是两个元素,元素中的字符串保留行尾换行符。"w"与"w+"的区别是什么?参考解答
两种模式都会清空已有文件、在文件不存在时创建;
+只增加读取能力。C的
fopen打开失败时返回什么?参考解答
fopen失败返回空指针,具体原因记录在errno中,可以用perror打印。EOF是fclose失败等场景的返回值。关于C的文件定位与状态判断,哪些叙述正确?
参考解答
ftell返回当前位置指示值,不是元素个数;文本流的位置值不应一概当作字节偏移。元素个数是fread、fwrite的返回值。fread(ptr, size, nmemb, stream)的返回值表示什么?参考解答
返回值以元素为单位,元素大小由
size指定。只有返回值等于nmemb才表示完整读到了请求的数量。Python中
with open(...) as f:的主要作用是什么?参考解答
with保证退出代码块时调用close(),包括因异常退出的情况;它不加锁,也不负责读取或创建文件。关于两套接口的文本与二进制处理,哪些叙述正确?
参考解答
文本模式要求写入
str,写bytes会抛TypeError;需要按字节读写时应显式使用二进制模式。