Python: for-iteration through a utf-8 string -> 迭代器的数据 type/encoding 是什么?

Python: for-iteration through a utf-8 string -> what's the data type/encoding of the iterators?

我有一个utf-8编码的字符串(主要是中文+一些英文),想运行算一个字母。 (类似于英语单词计数)。

所以我用了

for letter in text:    # text is a utf-8 encoded str

但我不确定 'letter' 我得到了什么。 'text' 在屏幕上打印和写入 csv 都很好。但是 'for letter in text' 中的 'letter' 在屏幕和 csv 文件中看起来都崩溃了。我认为这肯定是与编码相关的一些问题,但是在这里和那里添加 .encode('utf-8') 并不能解决问题并且 return 像

这样的错误
UnicodeDecodeError: 'ascii' codec can't decode byte 0x83 in position 0: ordinal not in range(128)

我的意思是下面的代码没有 return 错误但是字母看起来都崩溃了,当我添加 .encode('utf-8') 至 打印 letter.encode('utf-8')wcwriter.writerows([[k.encode('utf-8'), v]])

# -*- coding: utf-8 -*-
...

with open(fname+'.csv', 'wb') as twfile:
    twwriter = csv.writer(twfile)
    twwriter.writerows([[u'Date/Time', u'Text', u'ID', u'Location', u'City', u'Province']])

    for statuses in jres.get('statuses'): # jres is a json format response returned from a API call request
        text = statuses.get('text').encode('utf-8')

        if keyword in text:
            td = statuses.get('created_at').encode('utf-8')
            name = statuses.get('user').get('screen_name').encode('utf-8')
            loc = statuses.get('user').get('location').encode('utf-8')
            city = statuses.get('user').get('city').encode('utf-8')
            province = statuses.get('user').get('province').encode('utf-8')

            twwriter.writerows([[td, text, name, loc, city, province]])
            keycount += 1

# this is the problematic part. I'm not sure exactly what data type or encoding I'm getting for 'letter' below 

            for letter in text:
                if letter not in dismiss:
                    print letter   # this will print a lot of crushed letters
                    if letter not in wordcount:
                        wordcount[letter] = 1
                    else:
                        wordcount[letter] += 1

with open(wcname+'.csv', 'wb') as wcfile:
    wcwriter = csv.writer(wcfile)
    wcwriter.writerows([[u'Letter', u'Number']])

    for k, v in wordcount.items():
        wcwriter.writerows([[k, v]])

UTF-8 编码的字节可以很好地打印到屏幕或写入文件,但这只是因为您的屏幕(终端或控制台)和任何读取文件的内容也能理解 UTF-8。

UTF-8 编码每个代码点使用 一个或多个字节。迭代不是逐个代码点而是逐字节地遍历数据代码点。所以字符 'å' 被编码为 UTF8 为两个字节,C3 和 A5。尝试将这两个字节作为字母处理会产生问题:

>>> 'å'
'\xc3\xa5'
>>> for byte in 'å':
...     print repr(byte)
... 
'\xc3'
'\xa5'

你应该解码unicode值,这样Python知道字节编码的代码点,或者你已经有Unicode的地方,编码:

>>> for codepoint in 'å'.decode('utf8'):
...     print repr(codepoint), codepoint
... 
u'\xe5' å

当您尝试对已经编码的字节进行编码时,会导致您的异常。 Python 试图通过首先将字节解码为 Unicode 来提供帮助,以便它可以遵守并编码回字节,但它只能使用默认的 ASCII 编码来实现。这就是为什么你在尝试使用 encode():

时得到 UnicodeDecodeError(注意那里的 Decode
>>> 'å'.encode('utf8')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 0: ordinal not in range(128)

一般来说,您希望尽可能将文本视为 Unicode。实施 Unicode sandwich,尽早从字节解码为 Unicode,并且仅在将数据写回文件时才编码,越晚越好。您正在处理的 JSON 数据已经是 Unicode,因此您只需要在生成 CSV 行时编码为 UTF8,但不是更早的

在这种情况下,这意味着您应该编码text:

for statuses in jres.get('statuses'): # jres is a json format response returned from a API call request
    text = statuses['text']

而是仅在将其传递给 CSV 编写器时对其进行编码:

twwriter.writerows([[td, text.encode('utf8'), name, loc, city, province]])

您可能想研究一下 Unicode 和编码之间的区别,以及它们与 Python 的关系:

即使使用解码的 utf-8,Python 似乎也将表情符号等分成多个代码点。我使用以下函数解决了这个问题:

# ustr must be "decoded" unicode string, e.g. u""
def each_utf8_char(ustr, pointer=0):
  ustr = ustr.encode('utf-8')
  slen = len(ustr)
  char = ustr[pointer] if slen > pointer else False
  while char:
    charVal = ord(char)
    if charVal < 128:
      bytes = 1
    elif charVal < 224:
      bytes = 2
    elif charVal < 240:
      bytes = 3
    elif charVal < 248:
      bytes = 4
    elif charVal == 252:
      bytes = 5
    else:
      bytes = 6
    yield ustr[pointer:pointer+bytes].decode('utf-8')
    pointer += bytes
    char = ustr[pointer] if slen > pointer else False

它是一个生成器,所以你可以这样使用它:

my_ustr = u' Cheers!'
for char in each_utf8_char(my_ustr):
  print char