文件基本操作

对文件的操作有很多种, 常见的操作包括创建, 删除, 修改权限, 读取, 写入等, 这些操作可大致分为两类:

  • 删除, 修改权限: 作用于文件本身, 属于系统系操作;
  • 写入, 读取: 是文件最常用的操作, 作用于文件的内容, 属于应用级别操作.

对于文件的系统级操作功能单一, 比较容易实现, 可以借助Python中的专用模块(os, sys等), 调用指定的函数来实现. 而对于文件的应用级操作, 通常需要按照固定的步骤进行, 且实现过程相对比较复杂.

文件的应用级操作可以分为以下3步, 每一步都需要借助对应的函数实现:

  • 打开文件: 使用open()函数, 该函数会返回一个文件对象;
  • 对已打开文件做读/写操作: 读取文件内容可使用read(), readline()readlines()函数, 向文件中写入内容, 可以使用write()函数;
  • 关闭文件: 对文件的读/写操作完成之后, 最后需要关闭文件, 可以使用close()函数.

一个文件, 必须在打开之后才能对其进行操作, 并且在操作结束之后, 还应该将其关闭.

创建/打开文件

如果想要操作文件, 首先需要创建或者打开指定的文件, 并创建一个文件对象, 这些工作可以通过内置的open()函数实现.

open()函数用于创建或打开指定文件, 其常用语法格式如下:

f = open(filename, mode='r', buffering=-1, encoding=None)
  • file: 创建的文件对象;
  • filename: 要创建或打开的文件的名称;
  • mode: 可选参数, 指定文件的打开模式;
  • buffering: 可选参数, 用于指定对文件进行读写操作时, 是否使用缓冲区;
  • encoding: 指定打开文件时所使用的编码格式, 不同平台的encoding参数值也不同.

注解

系统中默认有三个标准流: sys.stdin(标准输入流), sys.stdout(标准输出流), sys.stderr(标准错误流).

可以读sys.stdin, 可以写sys.stdoutsys.stderr, 就像使用文件对象一样.

文件打开模式

文件的访问模式有: r, w, x, a, b, t, +.

  • 模式参数是可选的, 默认是rt(读/文本文件);
  • +可以与其它任何模式组合使用, 表示在原先的打开模式基础之上, 是既可读也可写的.
文件打开模式
模式 描述
r 只读模式, 若文件不存在, 则引发FileNotFoundError异常
w 只写模式, 若文件已存在将其覆盖, 若文件不存在则创建新文件
a 追加模式, 若文件已存在将文件指针放在文件结尾, 若文件不存在则创建新文件
x 独占模式, 若文件已存在则引发FileExistsError 异常

文件格式和二进制格式打开文件的区别

实际上, 从计算机的角度来看, 文本文件和二进制文件没有区别, 它们的内容都是以二进制的形式保存在磁盘中的. 对于文本文件, 是将字符按一定的格式编码后(ASCII, UTF-8, GBK等)存储起来, 而对于二进制文件, 是直接将数据按其二进制格式保存. 文本文件通常是可读的, 因为文本文件打开之后, 编辑器根据编码格式, 将其中的二进制数据转换为字符; 二进制文件通常用来保存视频, 图片, 音频等不可读的数据.

使用文件格式和二进制格式打开文件, 唯一的区别是对文件中换行符的处理不同.

在Windows系统中, 文件使用\r\n作为换行符, 当以文本格式读取文件时, 会将\r\n转换成\n; 反之, 以文本格式将数据写入文件时, 会将\n转换成\r\n. 这种隐式转换换行符的行为, 对用文本格式格式打开文件是没有问题的, 但如果用文本格式打开二进制文件, 就有可能改变文件中的数据(将\r\n隐式转换为\n).

而在Unix/Linux系统中, 默认的文件换行符就是\n, 因此在Unix/Linux系统中文本文件和二进制文件并无本质的区别.

总的来说, 为了保险起见, 对于Windows平台最好用b打开二进制文件, 对于Unix/Linux平台, 打开二进制文件, 可以用b, 也可以不同.

是否使用缓冲区

通常情况下, 建议在使用open()函数时打开缓冲区, 即不需要修改buffering参数的值.

注解

如果buffering参数的值为0(或者False), 则表示在打开指定文件时不使用缓冲区; 如果buffering参数值为大于1的整数, 该整数用于指定缓冲区的大小(单位是字节); 如果buffering参数的值为负数, 则代表使用默认的缓冲区大小.

为什么呢? 原因很简单, 目前计算机内存的I/O速度仍远远高于外设(例如键盘, 鼠标, 硬盘等)的I/O速度, 如果不使用缓冲区, 则程序在执行I/O操作, 内存和外设就必须进行同步读写操作, 也就是说, 内存必须等待外设输入(输出)一个字节之后,才能再次输入(输出)下一个字节. 这意味着, 内存中的程序大部分时间都处于等待状态.

而如果使用缓冲区, 则程序在执行输出操作时, 会先将所有数据输出至缓冲区, 然后继续执行其它操作, 缓冲区中的数据会有外设自行读取处理; 同样, 当程序执行输入操作时, 会先等外设将数据读入缓冲区中, 无需同外设做同步读写操作.

文件对象的常用属性

成功打开文件之后, 可以调用文件对象的属性获取当前文件的部分信息, 其常见属性如下:

  • file.name: 文件的名称;
  • file.mode: 文件的打开模式;
  • file.encoding: 打开文件时使用的编码格式;
  • file.closed: 判断文件是否已经关闭.

注解

文件最重要的功能就是读写数据.

在文本和二进制模式下, 分别将strbytes类用作数据.

读取文件

读取文件有3个方法可用:

  • read(): 逐个字符或字节读取文件;
  • readline(): 读取一行;
  • readlines(): 一次性读取文件中的所有行.

read()

f.read(size)

size作为一个可选参数, 用于指定一次最多可读取的字符(字节)个数, 如果省略, 则默认一次读取所有内容, 返回读取的字符串或字节对象. 注意, size表示的是一次最多可读取的字符(字节)数, 因此, 即便设置的size大于文件中存储的字符(字节)数, 也不会报错, 它只会读取文件中所有的数据直到文件结束.

注解

如果是以文本模式打开文件, 则read()会逐个字符进行读取; 如果是以二进制模式打开文件, 则read()会逐个字节进行读取.

read()抛出UnicodeDecodeError 异常的解决方法:

在使用read()时, 如果抛出UnicodeDecodeError异常, 其原因在于: 目标文件使用的编码格式和open()函数打开文件时指定的编码格式不一致.

举个例子, 如果目标文件的编码格式为GBK编码, 而我们在使用open()函数并以文本模式打开该文件时, 手动指定encoding参数为UTF-8. 这种情况下, 由于编码格式不匹配, 当使用read()读取目标文件文件时, Python解释器就会抛出UnicodeDecodeError异常.

要解决这个问题, 需要将open()函数中的encoding参数修改为和目标文件相同的编码格式.

除此之外, 还有一种解决方法: 先使用二进制模式读取读取文件, 然后调用bytesdecode()方法, 使用目标文件的编码格式, 将读取到的字节串解码为字符串.

readline()

f.readline(size)

size为可选参数, 用于指定读取一行时, 一次最多读取的字符(字节)数, 如果没有指定, 则默认读取一行直到遇到换行符(\n). 注意, 换行符(\n)也会被读取. 返回读取的字符串或字节对象.

readlines()

f.readlines()

readlines()函数用于读取文件中的所有行, 返回一个字符串列表, 其中每个元素为文件中的一行(包括行尾的换行符).

写入文件

write()

f.write(text)
  • text写入到文件中的当前位置(写入的可以是str, 也可以是bytes), 然后返回写入的字节数;
  • 如果哦text不是strbytes, 需要先进行转换;
  • 需要自己显式添加换行符.

Example:

with open('/tmp/test', 'w') as f:
    value = ('hello, world', 11)
    s = str(value) # 将非字符串类型转换为字符串
    f.write(s)

注意, 在写入文件完成后, 一定要调用close()函数将打开的文件关闭, 否则写入的内容不会保存到文件中. 这是因为, 当我们在写入文件内容时, 操作系统不会立刻把数据写入磁盘, 而是先缓存起来, 只有调用close()函数时, 操作系统才会保证把没有写入的数据全部写入磁盘文件中. 除此之外, 如果向文件中写入数据后, 不想马上关闭文件, 也可以调用文件对象的flush()方法, 它可以实现将缓冲区的数据写入文件中.

writelines()

f.writelines(iterable_object)

接收一个字符串列表(实际上可以是任何序列或可迭代对象)作为参数, 并将它们写入到文件(或)流中的当前位置, 需要自己显式添加换行符.

Example:

with open('a.txt', 'w') as f:
    l = ['hello', 'world', 'a', 'b', 'c']
    f.writelines(l)

关闭文件

close()方法用来关闭已经打开的文件:

f.close()
  • 处理完一个文件后, 应该总是使用f.close()来关闭文件并释放系统资源, 尽管这在有时候不是必需的, 但关闭文件没有什么害处;
  • 如果想确保文件被关闭, 可以使用try/finally语句, 并在finally子句中调用close()方法, 这样可以确保在任何情况下close()方法都被调用;
  • 可以使用with语句打开文件并将文件对象绑定到一个变量, 这样在离开with的范围之后, 会自动关闭打开的文件, 即使是由于异常引起的结束也是如此.

随机存取

注解

文件指针用于标明文件读写的位置. 通过移动文件指针, 再借助read()write()函数, 就可以实现在文件的指定位置读/写.

在Python中, 文件读写共用同一个文件指针.

为实现对文件指针的移动, 文件对象提供了tell()seek()方法. tell()方法用于返回文件指针当前所处的位置, seek()方法用于将文件指针移动到指定的位置.

tell()

f.tell()

返回当前文件指针的位置, 它是从文件开头计算的字节数.

当使用open()函数打开文件时, 文件指针的起始位置为0, 表示位于文件的开头处. 当使用文件对象读写数据时, 文件指针会自动向后移动: 读写了多少个数据, 文件指针就自动向后移动多少个位置.

seek()

f.seek(offset, whence)

将文件指针(读写的起始位置)移动到offsetwhence指定的位置.

  • whence: 可选参数, 用于指定文件指针要放置的位置, 该参数的值有三个选择:
    • io.SEEK_SET(0, 文件的开头, 默认值);
    • io.SEEK_CUR(1, 当前位置);
    • io.SEEK_END(2, 文件的末尾).
  • offset: 表示相对于whence位置文件指针的偏移量, 正数表示向后偏移, 负数表示向前偏移.

注意

offset为非0且whence不是0(文件开头)时, Python要求文件必须以二进制格式打开, 否则会抛出io.UnsupportedOperation异常.