【问题标题】:Python codecs line endingPython编解码器行尾
【发布时间】:2013-06-24 10:46:18
【问题描述】:

似乎 Python 的 UTF-8 编码(codecs 包)将 Unicode 字符 28、29 和 30 解释为行尾。为什么?我怎样才能阻止它这样做?

示例代码:

with open('unicodetest.txt', 'w') as f:
  f.write('a'+chr(28)+'b'+chr(29)+'c'+chr(30)+'d'+chr(31)+'e')
with open('unicodetest.txt', 'r') as f:
  for i,l in enumerate(f):
    print i, l
# prints "0 abcde" with special characters in between.

这里的重点是它像我期望的那样将其读取为一行。现在,当我使用 codecs 以 UTF-8 读取它时,它会将其解释为多行。

import codecs
with codecs.open('unicodetest.txt', 'r', 'UTF-8') as f:
  for i,l in enumerate(f):
    print i, l
# 0 a
# 1 b
# 2 c
# 3 de
# (again with the special characters after each a, b, c, d

字符 28 到 31 被描述为“信息分隔符四”到“一”(按此顺序)。有两件事让我印象深刻:1)28 到 30 被解释为行尾,2)31 不是。这是预期的行为吗?我在哪里可以找到哪些字符被解释为行尾的定义?有没有办法不将它们解释为行尾?

谢谢。

edit 忘记在codecs.open 中复制“UTF-8”参数。我的问题中的代码现已更正。

【问题讨论】:

  • 如果以'rb'模式打开文件会怎样?
  • 没什么区别。
  • @Paul,你可以回答你自己的问题,如果你愿意,可以接受它

标签: python unicode encoding utf-8 line-endings


【解决方案1】:

这是一个很好的问题。

使用open()codecs.open() 打开文件会有所不同。前者根据字节串​​进行操作。后者根据 Unicode 字符串进行操作。在 Python 中,这些 behave differently.

同样的问题出现在Python Issue 7643, What is a Unicode line break character?。讨论以及对 Unicode Character Database 的引用令人着迷。 Issue 7643 还给出了这个简洁的代码 sn-p 来演示差异:

for s in '\x0a\x0d\x1c\x1d\x1e':
  print u'a{}b'.format(s).splitlines(1), 'a{}b'.format(s).splitlines(1)

但归根结底就是这样。

为了确定字节字符串中的字节是否为换行符(或空格),Python 使用ASCII control characters 的规则。通过这种方法,字节 10 和 13 是换行符(并且 Python 将字节 13 后跟 10 视为单个换行符)。

但为了确定 Unicode 字符串中的字符是否为换行符,Python 遵循 Unicode Character Database 的字符分类,记录在 UAX #44UAX #14 Line Breaking Algorithm, section 5 Line Breaking Properties。根据问题 7643,这些文档标识了三个字符属性,这些属性将字符标识为 Python 目的的换行符:

  • 一般类别 Zl“行分隔符”
  • 一般类别 Zp“段落分隔符”
  • 双向 B 类“段落分隔符”

字符 28 (0x001C)、29 (0x001D) 和 30 (0x001E) 具有这些字符属性。字符 31 (0x001F) 没有。为什么?这是 Unicode 技术委员会的问题。但在 ASCII 中,这些字符被称为“文件分隔符”、“组分隔符”、“记录分隔符”和“单元分隔符”。使用选项卡式文本数据文件作为比较,前三个意味着至少与换行符一样多的分隔,而第四个可能类似于选项卡。

您可以在Objects/unicodeobject.c 中看到实际将这三个 Unicode 字符定义为 Python Unicode 字符串中的换行符的代码。查找数组ascii_linebreak[]。这个数组是unicode.splitlines() 实现的基础。 str.splitlines() 的底层代码不同。我相信,但没有在 Python 源代码中跟踪它,使用 codecs.open() 打开的文件上的 enumerate() 是根据 unicode.splitlines() 实现的。

你问,“我怎样才能阻止它这样做?”我看不出有任何方法可以让splitlines() 表现不同。但是,您可以将文件作为字节流打开,使用 str.splitlines() 行为将行读取为字节,然后将每一行解码为 UTF-8 以用作 unicode 字符串:

with open('unicodetest.txt', 'r') as f:
  for i,l in enumerate(f):
    print i, l.decode('UTF-8')
# prints "0 abcde" with special characters in between.

我假设您使用的是 Python 2.x,而不是 3.x。我的答案基于 Python 2.7。

【讨论】:

  • 谢谢。这是精心制作的。并感谢您的解决方案。有道理。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-03-01
  • 1970-01-01
  • 2017-03-15
  • 1970-01-01
  • 2015-11-06
  • 2011-06-29
  • 1970-01-01
相关资源
最近更新 更多