【问题标题】:Unpacking UTF-8 Encoded Line By Position按位置解包 UTF-8 编码的行
【发布时间】:2021-10-18 19:33:46
【问题描述】:

评论:虽然我理解对数据“准确性”的一些担忧,但我认为这对于这个问题来说真的无关紧要。数据就是这样。假设是这样,会对可能的解决方案感兴趣。

---------

我需要读取包含具有 unicode 字符的行的文件。这些行包含多列,每列都从一个固定位置开始。

这里是一个例子(我已经用连字符来表示单个列的结束位置,连字符实际上不是字符串的一部分)。 0004-1235957-A CORU¥A -ABC 在上面的示例中,第 1 列占据前 4 个位置,第 2 列占据接下来的 7 个位置,第 3 列占据接下来的 15 个位置,第 4 列占据接下来的 3 个位置,依此类推。

我正在使用以下代码来解析这一行:

line = '00041235957A CORU¥A       ABC\n'

formats = ('4s 7s 15s 3s')
st = struct.Struct(formats)
fields = tuple(s.decode() for s in st.unpack_from(line.encode()))
print(fields)

这是我得到的输出:

('0004', '1235957', 'A CORU¥A      ', ' AB')

如果您查看最后一列,它实际上被错误地读取了。原因是在 unicode 中,字符 '¥' 占用两个字节,这导致只有前 14 个字符(和 15 个字节)被读取,第 4 列从以下位置读取。

我想要的是一种读取第 3 列的 15 个 字符 而不是 15 个 字节 的方法,这与数据中存在的编码无关。即使我尝试在线使用子字符串方法,我也会得到相同的行为。

请专家对此提供一些指导吗?谢谢。

【问题讨论】:

  • 该示例看起来应该输入 A CORUÑA(它是西班牙的一个城市),带有一个带波浪号的字母 N,而不是日元货币符号。所以可能有一个解码步骤出错了。您可能正在处理 8 位编码,其中每个字符实际上是 1 个字节。你是怎么打开文件的?
  • 文件不包含 Unicode 字符。它们包含表示编码的 Unicode 字符的字节。编码很重要,可能看起来像单个 Unicode 字符的内容可能会占用多个字节,具体取决于编码。如果您只想拆分现有的 Unicode 字符串,只需使用字符串切片而不是编码为字节,或者如果您想使用 struct 模块,则使用单字节编码对其进行编码,但请注意垃圾输入垃圾输出。
  • @lenz 这是一个简单的mojibake 案例'Ñ'.encode( 'cp850').decode( 'cp1252') == '¥' 返回True...

标签: python utf-8


【解决方案1】:

当您line.encode() 时,默认编码为utf-8。正如你在下面看到的,¥ 符号变成了两个字节 \xc2\xa5 抛出你的计数:

>>> line = '00041235957A CORU¥A       ABC\n'
>>> line.encode()
b'00041235957A CORU\xc2\xa5A       ABC\n'

正如 cmets 所提到的,您可能首先误读了该文件,因为日元符号不太可能位于看起来像西班牙语单词的中间。确保您知道文件的原始编码是什么,以便正确读取文件。考虑以二进制格式读取文件,例如open(filename,'rb') 并将相关行的原始字节作为样本发布。

以下是两个“解决方案”:

import struct

line = '00041235957A CORU¥A       ABC\n'

st = struct.Struct('4s7s15s3s')
# encode with a single-byte encoding so the length doesn't change.
fields = tuple(s.decode('latin1') for s in struct.unpack_from(line.encode('latin1')))
print(fields)

# slice the string directly (RECOMMENDED)
fields = line[:4],line[4:11],line[11:26],line[26:29]
print(fields)

输出:

('0004', '1235957', 'A CORU¥A       ', 'ABC')
('0004', '1235957', 'A CORU¥A       ', 'ABC')

【讨论】:

    【解决方案2】:

    您是否想以可读、可重复的方式按字符划分行,您可以使用slice 对象。

    >>> s1 = slice(0, 4)
    >>> s2 = slice(4, 11)
    >>> s3 = slice(11, 26)
    >>> s4 = slice(26, 29)
    >>> slices = line[s1], line[s2], line[s3], line[s4]
    >>> slices
    ('0004', '1235957', 'A CORU¥A       ', 'ABC')
    

    切片对象在功能上与从字符串或列表中获取切片相同,例如s1 = line[0:4],但它们独立于被切片的序列。


    正如 cmets 和其他答案中所指出的,看起来数据可能没有被正确解码。知道字节 b'\xa5' 在编码 cp1252、cp1253、cp1254、cp1255、cp1256、cp1258、iso8859_15、iso8859_8、iso8859_9、latin_1、palmos 中映射到“¥”可能会有所帮助,但映射到“Ñ‎”在编码 cp437、cp850、cp857、cp858、cp860、cp862、cp865(source)中。

    【讨论】:

      【解决方案3】:

      str.encodebytes.decode 的默认编码是 utf-8(这是一种可变宽度字符编码)。请改用固定长度编码。应用utf-32 以正确处理BMP 以外的字符。

      import struct
      
      line = '00041235957A CORU¥A       ABC\n'
      
      formats = ('4s 7s 15s 3s')
      formats32 = '<' + ''.join([str(4*int(x))+'s' for x in formats.split('s')[:-1]])
      st = struct.Struct(formats32)
      fields = tuple(s.decode('utf_32_le') for s in st.unpack_from(line.encode('utf_32_le')))
      print(fields)
      

      结果:.\SO\69621626.py

      ('0004', '1235957', 'A CORU¥A       ', 'ABC')
      

      【讨论】:

        猜你喜欢
        • 2012-11-07
        • 2019-05-07
        • 2019-08-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-06-15
        • 2011-09-02
        • 1970-01-01
        相关资源
        最近更新 更多