PEP 3138 – Python 3000 中的字符串表示形式
- 作者:
- Atsuo Ishimoto <ishimoto at gembook.org>
- 状态:
- 最终版
- 类型:
- 标准跟踪
- 创建日期:
- 2008年5月5日
- Python 版本:
- 3.0
- 发布历史:
- 2008年5月5日,2008年6月5日
摘要
本 PEP 提出了 Python 3000 的新字符串表示形式。在 Python 3000 之前的 Python 版本中,内置函数 repr() 将任意对象转换为可打印的 ASCII 字符串,用于调试和日志记录。对于 Python 3000,应将基于 Unicode 标准的更广泛的字符范围视为“可打印”。
动机
当前的 repr() 使用以下算法将 8 位字符串转换为 ASCII。
- 将 CR、LF、TAB 和“\”转换为“\r”、“\n”、“\t”、“\\”。
- 将其他不可打印字符(0x00-0x1f、0x7f)和非 ASCII 字符(>= 0x80)转换为“\xXX”。
- 反斜杠转义引号字符(撇号,“)并在开头和结尾添加引号字符。
对于 Unicode 字符串,执行以下附加转换。
- 将没有尾随字符的前导代理对字符(0xd800-0xdbff,但不跟随 0xdc00-0xdfff)转换为“\uXXXX”。
- 将 16 位字符(>= 0x100)转换为“\uXXXX”。
- 将 21 位字符(>= 0x10000)和代理对字符转换为“\U00xxxxxx”。
该算法将任何字符串转换为可打印的 ASCII,并且 repr() 被用作一种方便且安全的方式来打印字符串,用于调试或日志记录。尽管所有非 ASCII 字符都被转义,但这在字符串的大多数字符是 ASCII 时无关紧要。但对于其他语言,例如日语,其中字符串中的大多数字符不是 ASCII,这非常不方便。
我们可以使用 print(aJapaneseString) 来获得可读的字符串,但我们没有类似的变通方法来打印来自列表或元组等集合中的字符串。print(listOfJapaneseStrings) 使用 repr() 来构建要打印的字符串,因此生成的字符串始终是十六进制转义的。或者当 open(japaneseFilename) 引发异常时,错误消息类似于 IOError: [Errno 2] No such file or directory: '\u65e5\u672c\u8a9e',这没有帮助。
Python 3000 为非拉丁语用户提供了许多不错的特性,例如非 ASCII 标识符,因此如果 Python 也能在可打印输出方面以类似的方式进步,那将非常有帮助。
一些用户可能会担心,如果他们打印图像等二进制数据,此类输出会弄乱他们的控制台。但这种情况在实践中不太可能发生,因为字节和字符串在 Python 3000 中是不同的类型,因此将图像打印到控制台不会弄乱它。
Hye-Shik Chang [1] 曾讨论过这个问题,但被驳回了。
规范
- 在 Python C API 中添加一个新函数
int Py_UNICODE_ISPRINTABLE (Py_UNICODE ch)。如果 repr() 应该转义 Unicode 字符ch,则此函数返回 0;否则返回 1。应转义的字符在 Unicode 字符数据库中定义为- Cc (其他,控制)
- Cf (其他,格式)
- Cs (其他,代理)
- Co (其他,私用区)
- Cn (其他,未分配)
- Zl (分隔符,行),指 LINE SEPARATOR('\u2028')。
- Zp (分隔符,段落),指 PARAGRAPH SEPARATOR('\u2029')。
- Zs (分隔符,空格) 除了 ASCII 空格('\x20')。此类别中的字符应转义以避免歧义。
- 构建 repr() 字符串的算法应更改为
- 将 CR、LF、TAB 和“\”转换为“\r”、“\n”、“\t”、“\\”。
- 将不可打印的 ASCII 字符(0x00-0x1f、0x7f)转换为“\xXX”。
- 将没有尾随字符的前导代理对字符(0xd800-0xdbff,但不跟随 0xdc00-0xdfff)转换为“\uXXXX”。
- 将不可打印字符(Py_UNICODE_ISPRINTABLE() 返回 0)转换为“\xXX”、“\uXXXX”或“\U00xxxxxx”。
- 反斜杠转义引号字符(撇号,0x27)并在开头和结尾添加引号字符。
- 默认将 sys.stderr 的 Unicode 错误处理程序设置为“backslashreplace”。
- 在 Python C API 中添加一个新函数
PyObject *PyObject_ASCII (PyObject *o)。此函数使用 PyObject_Repr() 将任何 python 对象转换为字符串,然后对所有非 ASCII 字符进行十六进制转义。PyObject_ASCII()生成的字符串与 Python 2 中的PyObject_Repr()相同。 - 添加一个新的内置函数
ascii()。此函数使用 repr() 将任何 python 对象转换为字符串,然后对所有非 ASCII 字符进行十六进制转义。ascii()生成的字符串与 Python 2 中的repr()相同。 - 添加一个
'%a'字符串格式运算符。'%a'使用 repr() 将任何 python 对象转换为字符串,然后对所有非 ASCII 字符进行十六进制转义。'%a'格式运算符生成的字符串与 Python 2 中的'%r'相同。此外,向string.format()方法添加'!a'转换标志,并向 PyUnicode_FromFormat() 添加'%A'运算符。它们将任何对象转换为 ASCII 字符串,就像'%a'字符串格式运算符一样。 - 向字符串类型添加一个
isprintable()方法。str.isprintable()如果 repr() 会转义字符串中的任何字符,则返回 False;否则返回 True。isprintable()方法内部调用Py_UNICODE_ISPRINTABLE()函数。
基本原理
Python 3000 中的 repr() 应该是基于 Unicode 的,而不是基于 ASCII 的,就像 Python 3000 字符串一样。此外,转换不应受区域设置的影响,因为区域设置不一定与输出设备的区域设置相同。例如,守护进程通常在 ASCII 设置中调用,但将 UTF-8 写入其日志文件。此外,Web 应用程序可能希望以基于 HTML 页面编码的更可读形式报告错误信息。
用户控制台不支持的字符在打印时可以通过 Unicode 编码器的错误处理程序进行十六进制转义。如果输出文件的错误处理程序是“backslashreplace”,则此类字符在不引发 UnicodeEncodeError 的情况下进行十六进制转义。例如,如果默认编码是 ASCII,print('Hello ¢') 将打印“Hello \xa2”。如果编码是 ISO-8859-1,则会打印“Hello ¢”。
sys.stdout 的默认错误处理程序是“strict”。读取输出的其他应用程序可能无法理解十六进制转义字符,因此在写入时应捕获不支持的字符。如果必须转义不支持的字符,则应明确更改错误处理程序。与 sys.stdout 不同,sys.stderr 默认不引发 UnicodeEncodingError,因为默认错误处理程序是“backslashreplace”。因此,将包含非 ASCII 字符的错误消息打印到 sys.stderr 不会引发异常。此外,解释器会在不引发异常的情况下打印有关未捕获异常的信息(异常对象、回溯)。
替代解决方案
为了在不更改 repr() 的情况下帮助非拉丁语语言的调试,提出了其他建议。
- 提供一个工具来打印列表或字典。
要打印用于调试的字符串不仅包含在列表或字典中,还包含在许多其他类型的对象中。文件对象包含 Unicode 文件名,异常对象包含 Unicode 消息等。当 repr()ed 时,这些字符串应以可读形式打印。实现一个工具来打印所有可能的对象类型是不可能的。
- 使用 sys.displayhook 和 sys.excepthook。
对于交互式会话,我们可以编写钩子来将十六进制转义字符恢复为原始字符。但这些钩子仅在打印交互式 Python 会话中输入的表达式的评估结果时调用,并且不适用于
print()函数、非交互式会话或logging.debug("%r", ...)等。 - 子类化 sys.stdout 和 sys.stderr。
很难实现一个子类来恢复十六进制转义字符,因为在它成为字符串时留下的信息不足以在所有情况下正确地撤消转义。例如,
print("\\"+"u0041")应该打印为“\u0041”,而不是“A”。但没有机会区分文件对象。 - 使 unicode_repr() 使用的编码可调,并将现有的 repr() 作为默认值。
使用可调 repr(),使用 repr() 的结果是不可预测的,并且会使编写涉及 repr() 的正确代码变得不可能。如果当前的 repr() 是默认值,那么旧的约定保持不变,用户可能会期望 ASCII 字符串作为 repr() 的结果。当使用自定义 repr() 函数时,第三方应用程序或库可能会感到困惑。
向后兼容性
更改 repr() 可能会破坏一些现有代码,尤其是测试代码。Python 的五个回归测试因此修改而失败。如果您需要不包含非 ASCII 字符的 repr() 字符串(如 Python 2),您可以使用以下函数。
def repr_ascii(obj):
return str(repr(obj).encode("ASCII", "backslashreplace"), "ASCII")
对于日志记录或调试,以下代码可能会引发 UnicodeEncodeError。
log = open("logfile", "w")
log.write(repr(data)) # UnicodeEncodeError will be raised
# if data contains unsupported characters.
为了避免引发异常,您可以显式指定错误处理程序。
log = open("logfile", "w", errors="backslashreplace")
log.write(repr(data)) # Unsupported characters will be escaped.
对于使用基于 Unicode 编码的控制台,例如 en_US.utf8 或 de_DE.utf8,反斜杠替换技巧不起作用,所有可打印字符都不会被转义。这将导致西方、希腊和西里尔语言中字符绘制相似的问题。这些语言使用相似(但不同)的字母表(源自共同祖先),并包含看起来相似但字符代码不同的字母。例如,很难区分拉丁语的“a”、“e”和“o”与西里尔语的“а”、“е”和“о”。(当然,视觉表示很大程度上取决于所使用的字体,但通常这些字母几乎无法区分。)为了避免这个问题,用户可以调整终端编码以获得适合其环境的结果。
被拒绝的提案
- 为内置 print() 函数添加 encoding 和 errors 参数,默认值分别为 sys.getfilesystemencoding() 和“backslashreplace”。
实现复杂,总的来说,这并不是一个好主意。[2]
- 使用字符名称而不是十六进制字符代码来转义字符。例如,
repr('\u03b1')可以转换为"\N{GREEK SMALL LETTER ALPHA}"。与十六进制转义相比,使用字符名称可能非常冗长。例如,
repr("\ufbf9")转换为"\N{ARABIC LIGATURE UIGHUR KIRGHIZ YEH WITH HAMZA ABOVE WITH ALEF MAKSURA ISOLATED FORM}"。 - sys.stdout 的默认错误处理程序应为“backslashreplace”。
写入 stdout 的内容可能会被另一个程序使用,该程序可能会错误地解释 \ 转义。对于交互式会话,可以将“backslashreplace”错误处理程序设置为默认值,但这可能会增加“在交互模式下有效但在重定向到文件时无效”之类的混淆。
实施
作者在 http://bugs.python.org/issue2630 中编写了一个补丁;该补丁已于 2008 年 6 月 11 日在修订版 64138 中提交到 Python 3.0 分支。
参考资料
版权
本文档已置于公共领域。
来源:https://github.com/python/peps/blob/main/peps/pep-3138.rst