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 实现。这给提供这些专门实现的编解码器作者带来了沉重负担,他们需要正确处理许多现在已由 io.TextIOWrapper 处理的边界情况(参见 附录 A)。虽然编解码器与流之间更深层次的集成理论上允许额外的优化,但这些优化在实践中要么没有实现,要么相关的代码重复意味着 io.TextIOWrapper 中已修复的边界情况在各种 StreamReader 和 StreamWriter 实现中仍未正确处理。
因此,本 PEP 提议:
- codecs.open() 在 Python 3.3 中更新为委托给内置的 open();
- 遗留的 codecs.Stream* 接口,包括 codecs.CodecInfo 的 streamreader 和 streamwriter 属性,在 Python 3.3 中废弃。
基本原理
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): codecs: StreamWriter 在 seek 或 append 模式下与有状态编解码器有关的问题
- 问题 #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)。
在 Python 2.6 之前,codecs.open() 是在 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 模块之间的主要区别在于,codecs 模块的错误必须在每个编解码器的 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