python中的文件IO之一



python 中有三种I/O, 分别是text I/O, binary I/O, raw I/O,
属于任意一种类型所对应的的对象都叫做file object, 也叫做stream, file-like object.

创建一个file object的通用方法是用open函数, 可以用来创建text I/O和binary I/O.
open函数的参数mode代表对文件操作的类型, 按照文件内容有二进制和纯文本,
按照操作类型可以分为以下几种, 摘自官方文档

  • 'r' open for reading (default)
  • 'w' open for writing, truncating the file first
  • 'x' create a new file and open it for writing
  • 'a' open for writing, appending to the end of the file if it exists
  • 'b' binary mode
  • 't' text mode (default)
  • '+' open a disk file for updating (reading and writing)

Text I/O

In [34]:
%%writefile myfile.txt
"这是一个测试的文本文件"
"hello can you hear me"
Overwriting myfile.txt
In [8]:
import io

# create a text stream
file1 = open('myfile.txt', 'r', encoding='utf8')

# create a in memory text stream
file2 = io.StringIO('texts stored in memory')

assert isinstance(file1, io.TextIOBase)
assert isinstance(file2, io.TextIOBase)

text stream对象的基类是TextIOBase, 我们重点关注以下几个方法

  • read(size=-1)
    按照size读取文本内容, 到达文件末尾时停止, size为负数或None时读取全部内容

  • readline(size=-1)
    按照size读取文本内容, 遇到换行符时或到达文件末尾时停止,
    size为负数或None或者读取一行全部内容, 如果已经在文件末尾, 返回空字符串

  • seek(offset, whence=SEEK_SET)
    改变文件指针的位置, 默认从0开始移动, offset是移动的偏移量, offset是指字节的大小, 一个中文字符是三个字节.
    whence可以是0, 1, 2代表从开始位置, 当前位置, 结束位置, 只有whence=0的时候, offset才可以有0以外的值

  • tell()
    返回当前的文件指针位置

  • write(s)
    写入文本

In [50]:
f = open('myfile.txt', 'r', encoding='utf8')
print("现在文件指针位置在%d" % f.tell())
print("让我们从开始位置读%d个字符: '%s'" % (10, f.read(10)))
print("现在文件指针位置在%d" % f.tell())
print("再从位置%d开始向后读取%d个字符: '%s'" % (f.tell(), 5, f.read(5)))
print("现在文件指针位置在%d" % f.tell())
print("最后从位置%d读取一行, size为%d: '%s'" % (f.tell(), 10, f.readline()))
print("现在文件指针位置在%d" % f.tell())
print("看看现在还剩下什么: '%s'" % f.read())
f.close()
现在文件指针位置在0
让我们从开始位置读10个字符: '这是一个测试的文本文'
现在文件指针位置在30
再从位置30开始向后读取5个字符: '件
hel'
现在文件指针位置在37
最后从位置37读取一行, size为10: 'lo can you hear me
'
现在文件指针位置在56
看看现在还剩下什么, ''

通过上面的例子可以看出:

  • 每次使用f.read() 或者 f.readline() 之后文件的指针都会移动, 具体视size的大小而定
  • f.read()或者f.readline() 每次都是从文件指针的位置开始向后读取size大小的字符
  • f.read(size) size指的是字符的大小, 而不是每个字所占的字节的大小
  • f.tell() 返回的位置是按照字节大小来计算的, 一个中文占三个字节, 一个字母占一个字节
  • 换行符也算一个字符, readline和read 返回的行文本包含了行尾的换行符
  • 因为文件指针一直在移动, 所以无法再读取已经读取过的内容, 除非手动移动指针的位置

下面来试试seek方法

In [61]:
f = open('myfile.txt', 'r', encoding='utf8')
print(f.read(1).encode('utf8'))
print("现在文件指针位置在%d" % f.tell())

f.seek(0)
print("现在文件指针位置在%d" % f.tell())

f.seek(1)
print("现在文件指针位置在%d" % f.tell())
try:
    f.read(1)
except UnicodeDecodeError:
    print("编码错误")
b'\xe8\xbf\x99'
现在文件指针位置在3
现在文件指针位置在0
现在文件指针位置在1
编码错误

前面说过f.tell()返回的位置是按照字节大小来计数, f.seek()也是按照字节数来移动文件指针, 而如果文本中有字符的字节数不为1的话, f.seek()可能会移动到某个字符的中间位置, 这时就会无法识别当前是什么字符, 这个例子中最后的f.seek(1) 之后指针所在位置的字节是\xbf, 无法与后续的字节构成一个合法的字符, 抛出UnicodeDecodeError.

读完本文, 希望你能够回答以下问题.

  1. 如何优雅的读写文件
    • 如何追加内容到文件的开始
    • 如何追加内容到文件的指定行
  2. 如何优化读写速度
    • 操作文件指针
    • 使用进程/线程锁确保数据一致性

Comments