Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

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 问题

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

最后修改:2025-02-01 08:59:27 GMT