在Python 3和Python 2中处理csv中的非utf8字符

Mik*_*e91 3 python csv unicode python-2.7 python-3.x

我有以下代码读取csv文件(一些包含非UTF8字符).它在Python 2.7.x中运行良好:

    encodings = {'ukprocessed.csv': 'utf8',
                 'usprocessed.csv': 'utf8',
                 'uyprocessed.csv': 'latin1',
                 'arprocessed.csv': 'latin1'}

    with codecs.open(filepath, 'r') as csvfile:
        reader = csv.reader(csvfile)
        for row in reader:
            row = [x.decode(encodings[filename]).encode('utf8') for x in row]
Run Code Online (Sandbox Code Playgroud)

但是,在Python 3.4.x中,测试失败并出现各种错误:

  • AttributeError:'str'对象没有属性'decode'
  • UnicodeDecodeError:'ascii'编解码器无法解码位置1078中的字节0xf1:序数不在范围(128)等...

我已经玩过在open文件中指定'encoding =',用'rb'和其他一些东西打开字节,但我找不到适用于Python 2和3的解决方案.

有没有人对我如何解决这个问题有任何想法?

谢谢

Sha*_*ger 7

狭隘地解决您的错误原因:

在Py3中,x每个row中的值都是str(类似于Py2 unicode).在Py2中,str并且unicode过于灵活,因为str它既是文本又是二进制数据类型; 它支持encode,通过假设str是ASCII,解码它,然后重新编码为选择的编码(对于ASCII兼容编解码器是没有意义的,因为遇到非ASCII时会出错).对于对称性,允许类似的容易出错和无意义decodeunicode类型; 它将encodeASCII(如果unicode包含非ASCII,则出错),然后decode在请求的编解码器中.这是各种误解,错误等的根源.

在Python 3中,他们更好地分割了类型:

  1. str文本类型,并且只具有一个encode方法(从逻辑字符转换为所述字符的特定二进制编码)
  2. bytes(和其他bytes类似的类型)表示二进制数据,并且只有一个decode方法(从特定的二进制编码转换为逻辑字符)

以便携方式解决您的问题:

你的代码要求"纯文本"类型支持decode(二进制 - >文本),正如我所指出的,Py2允许这在有限的意义上,即使它通常是愚蠢的.Py3没有; decode- 逻辑文本到逻辑文本是没有意义的,为了避免无声的错误行为,Py3不提供无效的方法(Py2将根据unicode对象的内容工作,然后在错误时失败;你会认为你的代码是非 - 英语友好,如果你真的使用它与非英语文本,它会中断).

csv如果您需要完全可移植性,编写必须处理非ASCII类型的代码并非易事.这是问题所在:

  1. 在Python 2中,您必须使用str(面向字节)编码作为一些不包含嵌入式NULs的编码,而不是unicode.注意: unicode如果它只包含返回的编码中的文本,则巧合工作sys.getdefaultencoding(),因为csv使用该值进行静默编码,通常asciisite模块在启动时配置sys.setdefaultencoding; 在调用之后site删除sys.setdefaultencoding,你不应该自己调整它; 当输入有任何不适合语言环境编码的时候,它会csv从强制转换unicode回来时中断str.这不仅仅是系统区域设置的问题; 在我的系统上,使用LANG=en_US.latin-1或者LANG=en_US.utf-8,Python仍然'ascii'以我的身份返回sys.getdefaultencoding().
  2. 在Python 3中,您必须使用str(面向文本,相当于Py2的unicode)

通常,对于非csv相关情况,我建议使用纯粹基于文本的类型io.open来获得Py2.7和Py3.x之间的完全兼容性(以及更好的性能/兼容性codecs.open).但是io.open(并且codecs.open就此而言)在文本模式下返回unicodePy2(csv除非它在默认编码中可以表示,否则不能使用,所以你认为它可以工作,直到你提供默认编码无法处理的东西),并且str在Py3(罚款); 在二进制模式下,它返回str上的Py2(罚款,如果没有内嵌NULS,虽然它不是解码你,所以你需要两个解码从strunicode,然后编码从后unicodeutf-8 str),并bytes在PY3(需要被解码str) .它很丑.

我可以给出的最佳解决方案是使用io.open,但是在特定步骤生成的迭代器周围添加一个依赖于版本的包装器,以确保迭代器的输出处于给定Python版本的适当形式(在Py3 utf-8str以Py2 编码str),您的一致行为(并将版本检查限制为每个文件执行固定次数,而不是每行一次):

import io
import sys

encodings = {'ukprocessed.csv': 'utf8',
             'usprocessed.csv': 'utf8',
             'uyprocessed.csv': 'latin1',
             'arprocessed.csv': 'latin1'}

# io.open in text mode will return unicode on Py2, str on Py3, decoded appropriately
# newline='' prevents it from doing line ending conversions (which are csv's
# responsibility)
with io.open(filepath, encoding=encodings[filepath], newline='') as csvdata:
    if sys.version_info[0] == 2:
        # Lazily convert lines from unicode to utf-8 encoded str
        csvdata = (line.encode('utf-8') for line in csvdata)
    reader = csv.reader(csvdata)
    if sys.version_info[0] == 2:
        # Decode row values to unicode on Py2; they're already str in Py3
        reader = ([x.decode('utf-8') for x in row] for row in reader)
    for row in reader:
        # operate on row containing native text types as values that can
        # represent whole Unicode range (unicode on Py2, str on Py3)
        ...
Run Code Online (Sandbox Code Playgroud)