UP | HOME

Python I/O

目录

1 简介

Python I/O 操作主要由模块 io 提供支持,在 Python3 中,内置函数 open 甚至就是 io.open 的别名。

这篇博客的主要内容就是对 io 模块的了解学习,并以 Python3 为主。

2 I/O 类型

I/O 主要有三种类型,分别为:

类型 作用
Text I/O 操作 字符
Binary I/O 操作 字节
Raw I/O 操作 字节 流, 无缓冲区

Raw I/O 和另外两种 I/O 直观的区别就是是否存在 缓冲区, 因此,你可以通过禁用缓冲区来创建原始流:

f = open("myfile.jpg", "rb", buffering=0)

3 Base Class

io 模块通过类的层次结构实现这些 I/O 类型,最基本的四个类为: IOBase, RawIOBase, BufferedIOBaseTextIOBase.

它们之间的关系可以通过源码获得(文档也行):

class IOBase(_io._IOBase, metaclass=abc.ABCMeta):
    __doc__ = _io._IOBase.__doc__

class RawIOBase(_io._RawIOBase, IOBase):
    __doc__ = _io._RawIOBase.__doc__

class BufferedIOBase(_io._BufferedIOBase, IOBase):
    __doc__ = _io._BufferedIOBase.__doc__

class TextIOBase(_io._TextIOBase, IOBase):
    __doc__ = _io._TextIOBase.__doc__

可以看到, IOBase 是一个 抽象类, RawIOBase, BufferedIOBaseTextIOBase 都继承自 IOBase.

对应到 I/O 类型的关系如下:

基类 I/O 类型
RawIOBase Raw I/O
BufferedIOBase Binary I/O
TextIOBase Text I/O

3.1 IOBase

IOBase 是所有 I/O 类的抽象基类,作用于字节流。我们不直接使用这个类,但可以了解这个类的字段和方法。

字段/方法 作用
closed 如果流已关闭,则为 True
close() 刷新缓冲区(如果存在)并关闭流
fileno() 获取流的 文件描述符, 不存在便抛出 OSError
flush() 刷新流的写缓冲区
isatty() 如果流是交互式的,则返回 True
readable() 如果可以读取流,则返回 True
readline(size=-1) 从流中读取并返回一行, size 限定读取字节大小
readlines(hint=-1) 从流中读取并返回行列表, hint 现在读取的行数
seek(offset[, whence]) 更改流当前的字节偏移量
seekable() 如果流支持随机访问,则返回 True
tell() 返回当前流的位置(字节偏移量)
truncate(size=None) 将流大小调整为给定大小,单位为字节,默认为流的当前位置
writable() 如果流支持写入,则返回 True
writelines(lines) 写一个行列表到流(换行符需要自己添加)

另外, IOBase 支持 上下文管理器, 即可以使用 with:

with open('spam.txt', 'w') as file:
    file.write('Spam and eggs!')

对于 seek, Python I/OC 实现,因此和 C 基本一致:

  • SEEK_SET(0) - 流的开始
  • SEEK_CUR(1) - 当前流的位置
  • SEEK_END(2) - 流的结束

seek 的参数 offset 指定相对于 whence 指定的位置的 字节偏移量.

3.2 RawIOBase

RawIOBase 继承了 IOBase, 因此拥有 IOBase 的所有字段和方法,除此以外还提供一下方法:

方法 作用
read(size=-1) 读取指定字节大小的内容,默认返回所有
readall() 读取并返回流中的所有字节
readinto(b) 将读取的字节存入对象 b, 并返回读取的字节数
write(b) 将对象 b 中的字节写入流,并返回写入的字节数

Raw I/O 操作的是 字节流, 因此无论是写入还是读取的内容都是 类字节对象.

错误的数据会导致异常,如 str.

3.3 BufferedIOBase

BufferedIOBase 同样继承自 IOBase, 是支持某种缓冲的二进制流的基类。

BufferedIOBase 的方法和字段:

字段/方法 作用
raw BufferedIOBase 内部处理的 Raw I/O 流(RawIOBase)
detach() 将基础原始流与缓冲区分开并返回
read(size=-1) 读取指定字节大小的内容,默认返回所有
readinto(b) 将读取的字节存入对象 b, 并返回读取的字节数
write(b) 将对象 b 中的字节写入流,并返回写入的字节数

方法 readreadinto 都有一个在后面加一个 1 的变形,分别为 read1readinto1.

这两个变形进行 I/O 操作是最多只调用一次 Raw I/O 操作。

3.4 TextIOBase

TextIOBase 支持的是 字符流, 方法和字段如下:

字段/方法 作用
encoding 字符编码格式
errors 解码器或编码器的错误设置
newlines 到目前为止已翻译的换行符
buffer TextIOBase 内部处理的 BufferedIOBase 对象
detach() 将基础二进制缓冲区与TextIOBase分开并返回
read(size=-1) 从流中读取指定数量的字符,默认读取所有
readline(size=-1) 读取一行内容, size 指定最多读取的字符数量
seek(offset[, whence]) 将流位置更改为给定的偏移量
tell() 获取流的当前位置
write(s) 将字符串写入流并返回写入字符数

4 具体实现

这三种 I/O 的具体实现还是不少的,这里不可能一一列举说明。但还是可以从源码了解具体实现有哪些:

RawIOBase.register(FileIO)  # RawIOBase

for klass in (BytesIO, BufferedReader, BufferedWriter, BufferedRandom,
              BufferedRWPair):
    BufferedIOBase.register(klass)  # BufferedIOBase

for klass in (StringIO, TextIOWrapper):
    TextIOBase.register(klass)  # TextIOBase
del klass

try:
    from _io import _WindowsConsoleIO
except ImportError:
    pass
else:
    RawIOBase.register(_WindowsConsoleIO)

类之间的层次结构图:

python-io.png

剩下的内容简单介绍一下 BytesIO, StringIOTextIOWrapper.

4.1 BytesIO

BytesIOBufferedIOBase 的实现,除了继承自 BufferedIOBase 的内容外,还有:

getbuffer()

获取缓冲区内容的可读写 视图

>>> b = io.BytesIO(b"abcdef")
>>> view = b.getbuffer()
>>> view[2:4] = b"56"
>>> b.getvalue()
b'ab56ef'
getvalue()
获取包含缓冲区全部内容的 bytes 对象

BytesIO 属于 I/O 对象,数据保存在 内存 中,有时,使用 BytesIO 来保存数据是一个很好的选择。

4.2 StringIO

StringIOTextIOBase 的实现,你可想操作文件那样操作它,它的方法 getvalue 会返回包含缓冲区全部内容的 字符串.

使用例:

import io

output = io.StringIO()
output.write('First line.\n')
print('Second line.', file=output)

# Retrieve file contents -- this will be
# 'First line.\nSecond line.\n'
contents = output.getvalue()

# Close object and discard memory buffer --
# .getvalue() will now raise an exception.
output.close()

4.3 TextIOWrapper

TextIOWrapper 是一个很重要的对象,平时我们通过 open('xxx') 获得就是这个对象。

它的第一个参数为一个 BufferedIOBase 对象,第二个参数为 encoding, 指定 编码格式.

通过使用 TextIOWrapper, 我们可以将一个 字节流 包装,指定 编码, 从而直接读写 字节流.

flaskjson 实现中就有一段这样的代码:

def _wrap_writer_for_text(fp, encoding):
    try:
        fp.write('')
    except TypeError:
        fp = io.TextIOWrapper(fp, encoding)
    return fp

通过这样的方式,我们可以方便而安全的操作 I/O 对象。

5 相关链接

版权声明:本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可