HOWTO · Python

修复 Python 中 str 没有属性解码的错误

本教程讨论 Python 中 str 没有属性解码错误。

本页内容

如果 Python 报告 AttributeError: 'str' object has no attribute 'decode',说明调用 decode() 的值是文本,而不是字节。在 Python 3 中,如果该值已经是 str,就移除这次调用。只有需要将 bytes 转换为文本时才使用 decode(),并在字节数据进入程序时完成转换。

理解 'str' object has no attribute 'decode' 错误

Python 3 将文本和字节序列视为不同类型。str 包含 Unicode 文本;bytes 值包含原始字节,这些字节可能表示经过编码的文本。decode() 方法属于字节类型,因为解码会按照某种字符编码解释字节,并生成 str。Python 3 的字符串并不是等待解码的已编码字节。

当代码对类型为 str 的值调用 decode() 时,就会出现此错误。例如,表达式 "Hello".decode("utf-8") 试图对文本执行字节操作。由于 str 没有 decode 方法,Python 会引发所报告的 AttributeError。这种情况常见于将旧的 Python 2 代码迁移到 Python 3,或某个库已经解码数据并将其作为结果返回时。

运行下面这个最小示例,以可选择的文本形式查看错误:
本节中的代码以及显示的标签或输出仍为英文。

try:
    "Hello".decode("utf-8")
except AttributeError as error:
    print(f"{type(error).__name__}: {error}")
AttributeError: 'str' object has no attribute 'decode'

因此,关键问题不是应该给 decode() 传入哪种编码,而是当前值是什么类型,以及它来自哪里。如果它是文本,就继续将它作为文本使用。如果它是字节,就使用数据源指定的编码解码一次。不要为了消除错误而额外添加转换。

为文本或字节选择正确的修复方法

如果值已经是文本,就移除 .decode(...) 调用并直接使用字符串。这是处理此错误的常规方法。例如,响应解析器、数据库驱动程序或以文本模式读取的文件如果返回了 str,就直接将该值传给下一个函数,不要再次解码。

运行下面的示例:
本节中的代码以及显示的标签或输出仍为英文。

message = "Hello, Python!"
print(message)
Hello, Python!

关键改动是直接将字符串传给 print;不需要进行编码再解码的往返转换。在出错的那一行也做相同修改:移除这次调用本身,而不是用另一种转换替代它。

encode() 执行相反的转换:它使用指定编码将文本转换为字节。当 API、文件格式或协议明确要求字节时,才使用它。如果中间没有任何面向字节的操作,将字符串编码成字节后又立即解码回原文本就是多余的。这并不能真正解决类型不匹配的问题,还可能让文本转换为字节的时机变得不清楚。

根据值的类型和接下来要执行的操作来选择方法:处理文本时保留 str;需要字节时调用 encode();需要将字节转换为文本时调用 decode()。方向很重要,因为编码定义字符如何映射为字节,并不意味着每个字符串都需要经过编码处理。

在数据边界解码字节

如果检查发现输入是 bytes 对象,解码就是合适的做法。应在字节进入程序的文本处理部分时解码一次,并使用数据源文档中说明的编码。UTF-8 很常见,但正确的编码取决于产生这些字节的文件、服务或协议。

本节中的代码以及显示的标签或输出仍为英文。

raw_data = b"caf\xc3\xa9"
text = raw_data.decode("utf-8")
print(repr(text))

这里的 raw_data 包含表示 café 的 UTF-8 字节,因此 decode("utf-8") 会将这些字节转换为字符串。这个非 ASCII 字符让编码边界更直观:在 UTF-8 中,它由多个字节表示,但在生成的文本中是一个字符。如果希望 Python 在读取文件内容时进行解码,请使用正确的编码以文本模式打开文件。如果读取的是二进制数据,或从套接字、库接收到字节,应在输入边界解码该字节值,再传给面向文本的代码。

空字节序列也是有效输入。下面的示例会在输入边界完成转换,不会将“没有内容”误认为解码失败:

raw_data = b""
text = raw_data.decode("utf-8")
print(repr(text))
''

根据应用程序对“没有内容”的定义处理得到的空字符串。相反,如果字节不符合声明的编码,就需要明确的处理策略,例如报告解码错误,或采用有文档说明的替换策略。不要只为了让解码成功就悄悄改用另一种编码。

避免在每次调用函数时都解码同一份内容。数据一旦转换为 str,下游函数就应接收文本,不应再次调用 decode()。如果下游 API 需要字节,则在该 API 的边界调用 encode()。将这些转换放在清晰的边界上,有助于确定由哪一层负责编码决策。

追踪并修正字符串的来源

要找到出错的值,请定位回溯信息指出的代码行,并检查 .decode() 前面的对象。使用 type(value) 或 isinstance(value, bytes) 检查其类型。追踪复杂的数据流时,可以使用下面这段简短的诊断代码:
本节中的代码以及显示的标签或输出仍为英文。

value = "already text"
print(type(value).__name__)

如果结果显示 str,就移除解码调用,并沿着该值追溯到产生它的函数、文件或响应。检查来源是否已经返回了解码后的文本。如果结果显示 bytes,就找出来源使用的编码,并在该值被当作文本处理之前完成解码。不要根据 raw_data 之类的变量名推断类型;应检查实际对象。

如果数据经过多个辅助函数,请明确规定它们的接口约定:处理文本的辅助函数应接收 str,处理字节的辅助函数应接收 bytes。例如,类型注解 def parse_message(message: str) -> None: 可以说明这种预期,但类型注解不会在运行时转换值。如果某个接口可能返回其中任一种类型,就在该接口处统一处理一次,并让程序其余部分保持一致。

总之,Python 3 的 str 值是文本,不提供 decode()。对于文本,移除这次调用;对于 bytes,在编码后的输入转换为文本时解码一次。这样既能解决这个具体错误,也能保留文本与其字节表示之间应有的区别。