9

似乎 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.

这里的要点是它像我期望的那样将其读取为一行。现在,当我使用codecsUTF-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 不是。这是预期的行为吗?我在哪里可以找到哪些字符被解释为行尾的定义?有没有办法不将它们解释为行尾?

谢谢。

编辑忘记将 'UTF-8' 参数复制到codecs.open. 我的问题中的代码现在已更正。

4

1 回答 1

6

这是一个很好的问题。

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

这个问题与Python 问题 7643,什么是 Unicode 换行符?. 讨论以及对Unicode 字符数据库的引用令人着迷。问题 7643 还提供了这个简洁的代码片段来演示差异:

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

但这归结为这一点。

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

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

  • 通用类别 Zl“分线器”
  • 通用类别 Zp“段落分隔符”
  • 双向 B 类“段落分隔符”

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

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

于 2013-09-29T09:14:40.697 回答