【Python】open 函数使用文件对象包装文件描述符

使用文件对象包装文件描述符

1. 读写文本数据

对文本数据的读写是一个常用的操作。通常,open 函数都可以满足需求。如果需要读取一个文本,可以使用 open 函数的 rt 模式:

>>> # read(),一次性读取全部文本
>>> with open("./file1", "rt") as f:
...     f.read()
... 
'This is 1 line.\nThis is 2 line.\nThis is 3 line.\nThis is 4 line.\n'
>>> # 逐行读取文本
>>> with open("./file1", "rt") as f:
...     for line in f:
...             print(line)
... 
This is 1 line.

This is 2 line.

This is 3 line.

This is 4 line.

>>>

如果需要对一个文本执行写操作,可以使用 open 函数的 wt 模式,如果文本已存在,该模式将会清除并覆盖原内容:

>>> # write(text),向文件写入文本
>>> with open("./file1", "rt") as f:
...     f.write(text)

如果要在已有文本后面追加内容,可以使用 open 函数的 at 模式:

>>> # write(text),向文件写入文本
>>> with open("./file1", "at") as f:
...     f.write(text)

默认情况下,文本的读取会使用系统默认的编码方式,系统默认编码可以使用 sys.getdefaultencoding() 来查询。

>>> sys.getdefaultencoding()
'utf-8'

注意

在使用 open() 函数时,有几点细节需要注意:

  1. 一般来说,使用 open 函数时会使用 with 语句为使用的文件创建一个上下文环境,当程序离开 with 后,文件自动关闭。如果不使用 with 语句,需注意在文件使用结束后,手动使用 f.close() 来关闭文件。
  2. 由于不同系统的换行符略有差别(UNIX的 ‘\n’ 和 WINDOWS的 ‘\r\n’),Python 默认工作在 “通用型换行符” 模式下。该模式下,所有常见的换行格式都能识别,并正确处理。如果不使用默认模式,可以通过 open 函数的 newline=‘\n’ 参数指定。
  3. open 函数也可以使用 encoding 参数手动指定编码格式,如果还会出现错误,还可以通过 errors 参数(replace/ignore)来指定如果处理编码错的字符。

2. 读写二进制数据

除文本数据外,通常也需要读取二进制数据,如图像、声音文件。简单地,可以使用 open 函数的 rb 或 wb 模式来实现读写二进制数据:

>>> with open('file2', 'rb') as f:
...     f.read()
... 
b'This is a line.\n'
>>> with open('file2', 'wb') as f:
...     f.write(b'This is an now line.')
... 
20

读取二进制数据时,所有的数据是以字节串的形式返回的,而不是文本字符串。字节串和字符串之间存在一些差异,在做索引和迭代操作时,字节串会返回整数值而不是字符串:

>>> for c in b'example':
...     print(c)
... 
101
120
97
109
112
108
101
>>> for c in 'example':
...     print(c)
... 
e
x
a
m
p
l
e

在执行写入操作时,可能会碰到这样一个需求:我们需要在只有文件不存在时,才执行写入操作。要实现这样的操作,可以使用 x 模式,使用 xt、xb 替换 wt、 wb:

>>> with open('file2', 'xb') as f:
...     f.write(b'This is an now line.')

3. open 函数使用文件对象包装文件描述符

open 函数以文本模式和以字节模式打开文件的处理有些不同。直觉上,字节到文件之间多了一个编码过程。那么,是否可以将以字节模式打开的文本重新添加编码呢?答案是可以的,使用 io.TextIOWrapper 装饰器可以达成这样的目的:


>>> with open('file2', 'rb') as f:
...     f.read()
... 
b'This is an example.\n'
>>> 
>>> 
>>> with open('file2', 'rb') as f:
...     f = io.TextIOWrapper(f, encoding='utf-8')
...     f.read()
... 
'This is an example.\n'
>>> 

这是如何实现的呢?首先观察文件对象 f :

>>> f = open('file2', 'r')
>>> f
<_io.TextIOWrapper name='file2' mode='r' encoding='UTF-8'>
>>> f.buffer
<_io.BufferedReader name='file2'>
>>> f.buffer.raw
<_io.FileIO name='file2' mode='rb' closefd=True>
>>>

可以看到,I/O 系统是以不同的层次来构建的,逐层包装文件描述符。首先最上层 io.TextIOWrapper 是一个文本处理层,它负责编码和解码 Unicode。下一层 io.BufferedReader 是一个缓冲 I/O 层,负责处理二进制数据。最后一层,io.FileIO 是原始文件在操作系统层面的文件描述符。所以,可以在最上层的 io.TextIOWrapper 添加和修改文本的编码。
注意

如果使用如下的形式修改编码,会出现问题:

>>> f = io.TextIOWrapper(f.buffer, encoding='utf-8')
>> f
<_io.TextIOWrapper name='file2' encoding='utf-8'>
>> f.read()
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
ValueError: I/O operation on closed file.

这是因为 f 之前的值已经被销毁,这个过程中底层的文件被关闭了。为了解决这个问题,可以使用文件对象的 detach() 方法,detach() 方法会将最上层的 io.TextIOWrapper 层分离出来并返回下一个层次 io.BufferedReader。

>>> f = open('file2', 'r')
>>> f.detach()
<_io.BufferedReader name='file2'>
f = io.TextIOWrapper(f.detach(), encoding='utf-8')

这种特征可以用来实现一些特别的操作,比如,将字节数据形式读取以文本模式打开的文件。可以通过直接访问文件对象的 buffer 实现,buffer 属性简单地指向了底层的文件,直接访问可以绕过文件对象的解码/编码层。

>>> f = open('file2', 'rt')
>>> f.read()
'This is an example.\n'
>>> # 因为文件指针已经直到了文件底部,所以再次读取时为空
>>> f.buffer.read()
b''
>>> f.close()
>>> 
>>> f = open('file2', 'rt')
>>> f.buffer.read()
b'This is an example.\n'
>>> f.close()
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值