PEP 400 – 弃用 codecs.StreamReader 和 codecs.StreamWriter
- 作者:
- Victor Stinner <vstinner at python.org>
- 状态:
- 已延期
- 类型:
- 标准跟踪
- 创建:
- 2011年5月28日
- Python 版本:
- 3.3
摘要
io.TextIOWrapper 和 codecs.StreamReaderWriter 提供相同的 API [1]。TextIOWrapper 具有更多功能并且比 StreamReaderWriter 更快。重复代码意味着错误需要修复两次,并且两种实现之间可能存在细微差别。
codecs 模块是在 Python 2.0 中引入的(参见 PEP 100)。io 模块是在 Python 2.6 和 3.0 中引入的(参见 PEP 3116),并在 Python 2.7 和 3.1 中使用 C 重新实现。
PEP 延期
由于缺乏当前对推动 PEP 目标、收集和整合反馈以及有足够可用时间有效地做到这一点感兴趣的维护者,因此对本 PEP 中涵盖的概念的进一步探索已被延期。
动机
当 Python I/O 模型在 3.0 中更新时,以 io.TextIOWrapper 的形式引入了“具有已知编码的流”的概念。由于此类对于 Python 3 中基于文本的 I/O 的性能至关重要,因此此模块具有优化的 C 版本,CPython 默认使用该版本。自 Python 3.0 发布以来,处理缓冲、有状态编解码器和通用换行符方面的许多特殊情况都已得到解决。
此新接口与 PEP 100 中原始编解码器接口设计的一部分的旧版 codecs.StreamReader、codecs.StreamWriter 和 codecs.StreamReaderWriter 接口大量重叠。这些接口围绕具有关联流的编码原则(即 io 模块中排列方式的反向)进行组织,因此原始 PEP 100 设计要求编解码器编写者除了核心编解码器 encode() 和 decode() 方法外,还提供适当的 StreamReader 和 StreamWriter 实现。这给编解码器作者带来了沉重的负担,要求他们提供这些专门的实现来正确处理许多特殊情况(参见 附录 A),而这些特殊情况现在已由 io.TextIOWrapper 处理。虽然在理论上编解码器和流之间更深入的集成允许进行额外的优化,但实际上这些优化要么尚未执行,要么相关的代码重复意味着 io.TextIOWrapper 中已修复的特殊情况在各种 StreamReader 和 StreamWriter 实现中仍然未得到正确处理。
因此,本 PEP 建议
- codecs.open() 在 Python 3.3 中更新为委托给内置的 open();
- 在 Python 3.3 中弃用旧版 codecs.Stream* 接口,包括 codecs.CodecInfo 的 streamreader 和 streamwriter 属性。
基本原理
StreamReader 和 StreamWriter 问题
- StreamReader 无法转换换行符。
- StreamWriter 不支持“行缓冲”(如果输入文本包含换行符则刷新)。
- CJK 编码(例如 GB18030)的 StreamReader 类仅支持 UNIX 换行符('\n')。
- StreamReader 和 StreamWriter 是有状态编解码器,但没有公开用于控制其状态的函数(getstate() 或 setstate())。每个编解码器都必须处理特殊情况,参见 附录 A。
- StreamReader 和 StreamWriter 与 IncrementalReader 和 IncrementalEncoder 非常相似,某些代码对于有状态编解码器(例如 UTF-16)是重复的。
- 每个编解码器都必须重新实现自己的 StreamReader 和 StreamWriter 类,即使它很简单(只需调用编码器/解码器)。
- codecs.open(filename, “r”) 创建一个 io.TextIOWrapper 对象。
- 没有编解码器在 StreamReader 或 StreamWriter 中基于编解码器的特性实现优化方法。
错误跟踪器中的问题
- 问题 #5445(2009-03-08):当传递生成器时 codecs.StreamWriter.writelines 问题
- 问题 #7262:(2009-11-04):codecs.open() + eol(windows)
- 问题 #8260(2010-03-29):当我使用 codecs.open(…) 并且 f.readline() 后跟 f.read() 返回错误结果时
- 问题 #8630(2010-05-05):编解码器 readline(s) 中的 Keepends 参数
- 问题 #10344(2010-11-06):codecs.readline 不关心缓冲
- 问题 #11461(2011-03-10):使用 codecs.readline() 读取 UTF-16 在代理对上中断
- 问题 #12446(2011-06-30):StreamReader Readlines 行为异常
- 问题 #12508(2011-07-06):编解码器异常
- 问题 #12512(2011-07-07):编解码器:seek 后或在追加模式下使用有状态编解码器的 StreamWriter 问题
- 问题 #12513(2011-07-07):codec.StreamReaderWriter:交错读写问题
TextIOWrapper 特性
- TextIOWrapper 支持任何类型的换行符,包括转换换行符(到 UNIX 换行符),以进行读取和写入。
- TextIOWrapper 重用编解码器的增量编码器和解码器(没有代码重复)。
- io 模块(TextIOWrapper)比 codecs 模块(StreamReader)更快。它是用 C 实现的,而 codecs 是用 Python 实现的。
- TextIOWrapper 具有一个预读算法,可以加快小型读取的速度:逐字符或逐行读取(在这些操作上,io 比 codecs 快 10 倍到 25 倍)。
- TextIOWrapper 具有写入缓冲区。
- TextIOWrapper.tell() 已优化。
- TextIOWrapper 支持使用单个类进行随机访问(读+写),从而允许优化交错读写(但尚未实现此类优化)。
TextIOWrapper 问题
- 问题 #12215(2011-05-30):TextIOWrapper:交错读写问题
StreamReader 和 StreamWriter 的可能改进
通过向 StreamReader 和 StreamWriter 类添加编解码器状态读/写函数,将有可能在基类中修复有状态编解码器的问题,而不是在每个有状态 StreamReader 和 StreamWriter 类中修复。
可以更改 StreamReader 和 StreamWriter 以使其使用 IncrementalDecoder 和 IncrementalEncoder。
编解码器可以实现针对特定编码进行优化的变体,或者拦截某些流方法以添加功能或提高编码/解码性能。TextIOWrapper 无法实现此类优化,但 TextIOWrapper 使用增量编码器和解码器并使用读写缓冲区,因此不完整输入的开销很低或为零。
对于其他可变长度编码编解码器(例如 UTF-8),可以做更多的事情,因为这些编解码器在读取结束附近通常会出现由于缺少字节而导致的问题。UTF-32-BE/LE 编解码器可以简单地将字符位置乘以 4 以获取字节位置。
StreamReader 和 StreamWriter 的用法
这些类很少直接使用,而是间接使用 codecs.open()。它们未使用在 Python 3 标准库中(除了 codecs 模块)。
一些项目使用 StreamReader 和 StreamWriter 实现自己的编解码器,但未使用这些类。
向后兼容性
保留公共 API,codecs.open
codecs.open() 可以用内置的 open() 函数替换。open() 具有类似的 API,但还具有更多选项。这两个函数都返回类文件对象(相同的 API)。
codecs.open() 是在 Python 2.6 之前以 Unicode 模式打开文本文件的唯一方法。许多 Python 2 程序使用此函数。删除 codecs.open() 意味着需要更多工作才能将程序从 Python 2 移植到 Python 3,尤其是对两个 Python 版本使用相同代码库(不使用 2to3 程序)的项目。
codecs.open() 保留是为了与 Python 2 保持向后兼容。
弃用 StreamReader 和 StreamWriter
在 Python 3.3 中,实例化 StreamReader 或 StreamWriter 必须发出 DeprecationWarning。定义子类不会发出 DeprecationWarning。
codecs.open() 将更改为重用内置的 open() 函数(TextIOWrapper)来读写文本文件。
替代方案
codecs.Stream* 类的弃用的一种替代方法是将 codecs.open() 重命名为 codecs.open_stream(),并创建一个新的 codecs.open() 函数重用 open(),从而使用 io.TextIOWrapper。
附录 A:有状态编解码器的相关问题
使用有状态编解码器和流很难正确使用。codecs 模块支持某些情况,而 io 没有更多与有状态编解码器相关的已知错误。codecs 模块和 io 模块之间的主要区别在于,错误必须在每个编解码器的 StreamReader 和/或 StreamWriter 类中修复,而错误只需在 io.TextIOWrapper 中修复一次。以下是有状态编解码器的一些问题示例。
有状态编解码器
Python 支持以下有状态编解码器
- cp932
- cp949
- cp950
- euc_jis_2004
- euc_jisx2003
- euc_jp
- euc_kr
- gb18030
- gbk
- hz
- iso2022_jp
- iso2022_jp_1
- iso2022_jp_2
- iso2022_jp_2004
- iso2022_jp_3
- iso2022_jp_ext
- iso2022_kr
- shift_jis
- shift_jis_2004
- shift_jisx0213
- utf_8_sig
- utf_16
- utf_32
读取和 seek(0)
with open(filename, 'w', encoding='utf-16') as f:
f.write('abc')
f.write('def')
f.seek(0)
assert f.read() == 'abcdef'
f.seek(0)
assert f.read() == 'abcdef'
io 和 codecs 模块正确支持此用例。
seek(n)
with open(filename, 'w', encoding='utf-16') as f:
f.write('abc')
pos = f.tell()
with open(filename, 'w', encoding='utf-16') as f:
f.seek(pos)
f.write('def')
f.seek(0)
f.write('###')
with open(filename, 'r', encoding='utf-16') as f:
assert f.read() == '###def'
io 模块支持此用例,而 codecs 失败,因为它在第二次写入时写入了一个新的 BOM (问题 #12512)。
追加模式
with open(filename, 'w', encoding='utf-16') as f:
f.write('abc')
with open(filename, 'a', encoding='utf-16') as f:
f.write('def')
with open(filename, 'r', encoding='utf-16') as f:
assert f.read() == 'abcdef'
io 模块支持此用例,而 codecs 失败,因为它在第二次写入时写入了一个新的 BOM (问题 #12512)。
链接
版权
本文档已进入公有领域。
脚注
来源:https://github.com/python/peps/blob/main/peps/pep-0400.rst