杰瑞科技汇

python str与unicode

核心思想:一句话总结

  • Python 2: str 是字节串,unicode 是真正的 Unicode 字符串,你需要手动在它们之间进行转换。
  • Python 3: str 是真正的 Unicode 字符串,bytes 是字节串,转换是自动的,但你需要明确地使用 .encode().decode()

Python 2 中的 strunicode

在 Python 2 中,这两种类型是明确分开的,这也是很多初学者和从 Python 2 迁移到 Python 3 的开发者感到困惑的地方。

python str与unicode-图1
(图片来源网络,侵删)

str 类型:字节串

  • 本质str 类型实际上是一个字节序列,它本身不关心这些字节代表的是哪个字符的编码,它只是原始的、未经处理的字节数组。
  • 编码:当你创建一个 str 字面量时(s = "你好"),Python 会根据你的源文件编码声明(通常是文件顶部的 # -*- coding: utf-8 -*-)或者系统的默认编码,将这些字符编码成特定的字节序列(如 UTF-8, GBK 等)。
  • 示例
    # 假设文件编码是 UTF-8
    s = "你好"
    print type(s)  # <type 'str'>
    print repr(s)  # u'\u4f60\u597d' (在内部,它被解释为 unicode,但存储为字节)
    # 更准确的查看方式是:
    # print [ord(c) for c in s] # 这可能会因为编码不同而得到错误的结果
    # 一个更清晰的例子:
    s_utf8 = "你好"
    # 在内存中,它存储的是 UTF-8 编码的字节: \xe4\xbd\xa0\xe5\xa5\xbd

unicode 类型:Unicode 字符串

  • 本质unicode 类型才是 Python 中真正意义上的字符串,它存储的是字符的码点,而不是字节,码点是每个字符在 Unicode 字符集中的唯一编号('A' 的码点是 U+0041,'中' 的码点是 U+4E2D)。
  • 字面量:在 Python 2 中,unicode 字面量需要在字符串前面加上 u 前缀,如 u"你好"
  • 优势unicode 对象是“编码无关”的,你可以在其中安全地操作任何语言的字符,而不用担心底层的字节表示。

Python 2 中的转换:encode()decode()

这是 Python 2 中最关键的操作。

  • unicode -> str (编码 - Encode):将一个 Unicode 字符串转换成特定编码的字节串。

    • 方法unicode_string.encode(encoding)
    • 示例
      u_str = u"你好"
      str_utf8 = u_str.encode('utf-8')
      print type(str_utf8)  # <type 'str'>
      print repr(str_utf8)  # '\xe4\xbd\xa0\xe5\xa5\xbd' (UTF-8 编码的字节)
  • str -> unicode (解码 - Decode):将一个字节串(str)根据指定的编码解码成 Unicode 字符串。

    • 方法str_string.decode(encoding)
    • 示例
      str_utf8 = '\xe4\xbd\xa0\xe5\xa5\xbd' # 这是一个 UTF-8 编码的字节串
      u_str = str_utf8.decode('utf-8')
      print type(u_str)  # <type 'unicode'>
      print repr(u_str)  # u'\u4f60\u597d' (真正的 Unicode 字符串)

Python 2 的常见问题:UnicodeDecodeError 和 UnicodeEncodeError

如果你不明确地进行转换,Python 2 会尝试使用默认的 ASCII 编码进行转换,这通常会导致错误。

  • UnicodeDecodeError:当你尝试将一个 str(字节串)当作文本处理,但没有指定正确的编码时发生。

    # str_utf8 是一个 UTF-8 字节串
    str_utf8 = '\xe4\xbd\xa0\xe5\xa5\xbd'
    # Python 2 默认尝试用 ASCII 解码
    print str_utf8.decode('ascii') 
    # UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)
  • UnicodeEncodeError:当你尝试将一个 unicode 字符串写入文件或打印到终端,但终端或文件的编码不支持该字符时发生。

    # u_str 是一个包含中文字符的 unicode 字符串
    u_str = u"你好"
    # 尝试用 ASCII 编码成字节串
    print u_str.encode('ascii')
    # UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)

最佳实践(Python 2)

  1. 内部处理全部使用 unicode,在你的程序逻辑中,所有文本数据都应该是 unicode 类型。
  2. 只在输入/输出时进行编码/解码,当从文件、网络、数据库或用户输入读取数据时,立即用正确的编码 decodeunicode,当需要写入或发送数据时,在最后一步用合适的编码 encodestr

Python 3 中的 strbytes

Python 3 彻底解决了 Python 2 的混乱局面,让字符串模型更加清晰和直观。

str 类型:Unicode 字符串

  • 本质:在 Python 3 中,str 类型就是真正的 Unicode 字符串,它的行为和 Python 2 中的 unicode 类型完全一样,它存储的是字符的码点,而不是字节。
  • 字面量:普通的字符串字面量()str 类型。
  • 示例
    s = "你好"
    print(type(s))  # <class 'str'>
    print(repr(s))  # '你好' (直接显示字符,而不是字节码)

bytes 类型:字节串

  • 本质bytes 类型是不可变的字节序列,它的行为和 Python 2 中的 str 类型非常相似。

  • 字面量bytes 字面量需要在前面加上 b 前缀,如 b"hello"

  • 特点

    • 它只能包含 ASCII 范围内的字符(0-127),对于非 ASCII 字符,你需要使用转义序列。
    • bytes 对象上的方法都是针对字节的操作(如 find, replace, split),而不是针对字符。
  • 示例

    b_str = b"hello"
    print(type(b_str))  # <class 'bytes'>
    print(repr(b_str))  # b'hello'
    # 对于非 ASCII 字符,必须使用转义
    b_str_chinese = b'\xe4\xbd\xa0\xe5\xa5\xbd' # 这是 UTF-8 编码的字节
    print(repr(b_str_chinese)) # b'\xe4\xbd\xa0\xe5\xa5\xbd'
    # 下面的代码会报错,因为 b"你好" 不是有效的字节字面量
    # b_str_invalid = b"你好" 
    # SyntaxError: bytes can only contain ASCII literal characters.

Python 3 中的转换:.encode().decode()

转换的概念和 Python 2 一样,但语法更清晰,并且是 strbytes 之间唯一的转换方式。

  • str -> bytes (编码 - Encode):将 Unicode 字符串编码成字节串。

    • 方法string.encode(encoding)
    • 示例
      s = "你好" # 这是一个 str (unicode)
      b_utf8 = s.encode('utf-8')
      print(type(b_utf8))  # <class 'bytes'>
      print(repr(b_utf8))  # b'\xe4\xbd\xa0\xe5\xa5\xbd'
  • bytes -> str (解码 - Decode):将字节串解码成 Unicode 字符串。

    • 方法bytes_object.decode(encoding)
    • 示例
      b_utf8 = b'\xe4\xbd\xa0\xe5\xa5\xbd' # 这是一个 bytes
      s = b_utf8.decode('utf-8')
      print(type(s))  # <class 'str'>
      print(repr(s))  # '你好'

Python 3 的优势

  1. 清晰性str 就是文本,bytes 就是二进制数据,类型本身的名字就说明了它的用途,消除了 Python 2 中的 strunicode 的混淆。
  2. 默认安全性:Python 3 在大多数情况下会强制你进行编码和解码,避免了隐式的、错误的 ASCII 转换。
    # Python 3
    s = "你好"
    # 尝试将 str 和 bytes 连接
    # s + b" world" 
    # TypeError: can only concatenate str (not "bytes") to str

    这个错误提示非常明确,告诉你不能直接拼接文本和二进制数据,你必须先统一类型。


总结与对比

特性 Python 2 Python 3
文本字符串 unicode str
字节串 str bytes
字面量 u"你好" "你好"
字节字面量 "hello" (编码后) b"hello"
编码 u_str.encode('utf-8') -> str str.encode('utf-8') -> bytes
解码 str_str.decode('utf-8') -> unicode bytes_obj.decode('utf-8') -> str
核心问题 strunicode 混淆,易出错 类型清晰,强制显式转换,更安全
哲学 "文本和字节可以自动转换"(但常常出错) "文本和字节是不同的东西,必须显式转换"

迁移建议

如果你正在将 Python 2 代码迁移到 Python 3,最核心的任务就是处理所有的字符串编码问题:

  1. 识别所有 str 对象:判断它们是真正的文本(应该变成 str)还是原始字节(应该变成 bytes)。
  2. 添加编码声明:确保你的 Python 2 源文件顶部有 # -*- coding: utf-8 -*-
  3. 使用 2to3 工具:这个工具可以自动处理大部分的语法转换,包括 unicode 字面量变成 str
  4. 处理 I/O:在 Python 3 中,文件操作默认使用系统编码,对于文本文件,最好在打开时明确指定编码:
    # Python 3
    with open('file.txt', 'r', encoding='utf-8') as f:
        content = f.read() # content 是 str

    对于二进制文件,使用 'rb''wb' 模式:

    # Python 3
    with open('file.dat', 'wb') as f:
        f.write(b'\x00\x01\x02') # 必须写入 bytes

希望这个详细的解释能帮助你彻底理解 Python 2 和 Python 3 中的字符串处理!

分享:
扫描分享到社交APP
上一篇
下一篇