【问题标题】:codecs.open(utf-8) fails to read plain ASCII filecodecs.open(utf-8) 无法读取纯 ASCII 文件
【发布时间】:2018-03-08 08:16:29
【问题描述】:

我有一个纯 ASCII 文件。当我尝试使用codecs.open(..., "utf-8") 打开它时,我无法读取单个字符。 ASCII是UTF-8的一个子集,为什么codecs不能以UTF-8模式打开这样的文件呢?

# test.py

import codecs

f = codecs.open("test.py", "r", "utf-8")

# ASCII is supposed to be a subset of UTF-8:
# http://www.fileformat.info/info/unicode/utf8.htm

assert len(f.read(1)) == 1 # OK
f.readline()
c = f.read(1)
print len(c)
print "'%s'" % c
assert len(c) == 1 # fails

# max% p test.py
# 63
# '
# import codecs
#
# f = codecs.open("test.py", "r", "utf-8")
#
# # ASC'
# Traceback (most recent call last):
#   File "test.py", line 15, in <module>
#     assert len(c) == 1 # fails
# AssertionError
# max%

系统:

Linux max 4.4.0-89-generic #112~14.04.1-Ubuntu SMP Tue Aug 1 22:08:32 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

当然,它适用于普通的open。如果我删除 "utf-8" 选项,它也可以工作。还有63 是什么意思?这就像第三行的中间。没看懂。

【问题讨论】:

  • 还打印出字符本身,而不仅仅是字符的长度和字节码。
  • 长度表明它也包括之前的 readline 结果;有趣。
  • 好吧,至少我可以在我的 Mac 上用 Python 版本 2.7.10、2.7.13 和 3.6.2 重现这个。 len(c) 对我来说在所有情况下都是 59。
  • 旁注:从不使用codecs.open。它以奇怪的方式存在缺陷(正如@Evert 所指出的,它必须以二进制模式打开文件,这会产生各种副作用)。试试using io.open instead(它与Py3 上的普通open 相同,并在Py2 上提供相同的接口),并且比codecs.open(基本上已弃用)更快更正确。我怀疑你的问题会消失。
  • @personal_clown: codecs.open 处于一种奇怪的状态。它严格不如io.open,除非在一些非常不寻常的情况下(字节字节编解码器,如ROT13和十六进制,而不是标准字节Unicode编解码器)。由于怪异的用例,他们一直不愿意正式弃用它,但如果你检查 Python 错误跟踪器,它通常被称为伪弃用。 PEP 400 includes a bunch of reasons 为什么StreamReader/StreamWriter/StreamReaderWritercodecs.open 创建的)坏了。

标签: python python-2.7 utf-8 readline codec


【解决方案1】:

发现你的问题:

当传递一个编码时,codecs.open 返回一个StreamReaderWriter,它实际上只是一个包装器(不是的子类;它是“组成”关系,而不是继承)@987654325 @ 和 StreamWriter。问题是:

  1. StreamReaderWriter 提供了一个“普通的”read 方法(也就是说,它需要一个 size 参数,仅此而已)
  2. 它委托给内部StreamReader.read method,其中size 参数只是关于要读取的字节数的提示,而不是限制; second 参数 chars 是一个严格的限制器,但 StreamReaderWriter 从不传递该参数(它不接受它)
  3. size 提示,但没有使用chars 封顶时,如果StreamReader 有缓冲数据,并且足够大以匹配size 提示StreamReader.read 盲目返回缓冲区的内容,而不是限制它以任何方式基于size 提示(毕竟,只有chars 施加了最大 返回大小)

StreamReader.read的API和size/chars对于API的含义是这里唯一记录的东西; codecs.open返回StreamReaderWriter的事实不是契约的,StreamReaderWriter包裹StreamReader的事实也不是,我只是使用ipython??魔法来阅读codecs模块的源代码来验证这种行为。但不管有没有记录,这就是它正在做的事情(请随意阅读StreamReaderWriter 的源代码,它都是 Python 级别的,所以很容易)。

最好的解决方案是切换到io.open,这在每种标准情况下都更快、更正确(codecs.open 支持无法在bytes [Py2 str] 和@987654356 之间转换的怪异编解码器@ [Py2 unicode],而是处理 strstrbytesbytes 编码,但这是一个非常有限的用例;大多数时候,你在 bytes 之间转换和str)。您需要做的就是导入io 而不是codecs,并将codecs.open 行更改为:

f = io.open("test.py", encoding="utf-8")

您的其余代码可以保持不变(并且可能会在启动时运行得更快)。

作为替代方案,您可以显式绕过StreamReaderWriter 以获取StreamReaderread 方法并直接传递限制参数,例如改变:

c = f.read(1)

到:

# Pass second, character limiting argument after size hint
c = f.reader.read(6, 1)  # 6 is sort of arbitrary; should ensure a full char read in one go

我怀疑Python Bug #8260,它涵盖了在codecs.open 创建的文件对象上混合readlineread,适用于此处,正式地,它是“固定的”,但是如果您阅读了 cmets,则修复不完整(鉴于文档化的 API,可能无法完成); readreadline 的任意奇怪组合将能够打破它。

再次,只需使用io.open;只要您使用的是 Python 2.6 或更高版本,就可以使用它,而且效果更好。

【讨论】:

  • 是的,我真的只是在开始混合readlineread 之后才开始看到我的问题。一定是你说的8260。感谢所有有用的研究。我将仔细研究io 和其他选项。
  • @personal_clown:是的,readline 会导致问题,因为为了避免过多的系统调用开销,它会缓冲(72 字节 IIRC)。如果您只是执行read(1) 调用,它将一次读取一个字节而无需缓冲(因为您“提示”您只需要一个字节并且它信任提示);您永远不会处于sizechars 重要的位置。但是readline 预先填充了缓冲区,所以现在size 没有chars 大致意味着“返回更大的当前缓冲区或size 字节的字符”。
猜你喜欢
  • 2015-05-20
  • 1970-01-01
  • 1970-01-01
  • 2011-10-26
  • 1970-01-01
  • 1970-01-01
  • 2017-08-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多