在 Python3 中对 print() 使用编解码器错误处理程序?

Use codec error handler for print() in Python3?

众所周知,encode() 有一个 error 参数用于编解码器错误处理,例如:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# "发" and "财" are not available in 'big5' encoding
text = "发财了".encode('big5', errors='replace')

但是,print() 没有 errors 参数,如果我们简单地写:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
print("发财了")

如果在使用 big5 编码的命令提示符中 运行,则会引发 UnicodeEncodeError 异常(例如,在繁体中文版本 Windows 中)。

有没有办法让 print()encode() 一样接受更多的处理程序,例如 replacebackslashreplacexmlcharrefreplace,所以字符串可以安全地打印而不会引发异常吗?

用 try .. except 块包装您的打印函数,并捕获 UnicodeEncodeError 异常:

try:
   print("发财了")
except UnicodeEncodeError:
   handle_encode_error()

您可以决定如何转换异常块中的字符串。

这是我目前的解决方案:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys

def safeprint(*args, errors='backslashreplace', **kargs):
    """
    Print safely and skips error decode.

    Acts like print() with an additional "errors" argument to determine the
    error handler for codec errors and accepts non-str-or-None types for the
    "sep" and "end" arguments.
    """
    e = (kargs['file'] if 'file' in kargs else sys.stdout).encoding
    args = [str(x) for x in args]
    sep = str(kargs['sep']) if 'sep' in kargs and kargs['sep'] is not None else " "
    end = str(kargs['end']) if 'end' in kargs and kargs['end'] is not None else "\n"
    text = sep.join(args) + end
    kargs['sep'] = ""
    kargs['end'] = ""
    print(text.encode(e, errors).decode(e, errors), **kargs)

if __name__ == "__main__":
    safeprint("Hello World!", "你好世界!", "ハローワールド", "हैलो वर्ल्ड")
    safeprint("Hello World!", "你好世界!", "ハローワールド", "हैलो वर्ल्ड", errors="ignore")
    safeprint("Hello World!", "你好世界!", "ハローワールド", "हैलो वर्ल्ड", errors="replace")
    safeprint("Hello World!", "你好世界!", "ハローワールド", "हैलो वर्ल्ड", errors="backslashreplace")
    safeprint("Hello World!", "你好世界!", "ハローワールド", "हैलो वर्ल्ड", errors="xmlcharrefreplace")
    safeprint("Hello World!", "你好世界!", "ハローワールド", "हैलो वर्ल्ड", sep=None, end=str)
    safeprint("Hello World!", "你好世界!", "ハローワールド", "हैलो वर्ल्ड", sep=" -发- ", end=" -财- \n")
    with open("safeprint_big5.log", "w", encoding="big5") as f:
        safeprint("Hello World!", "你好世界!", "ハローワールド", "हैलो वर्ल्ड", file=f)
    with open("safeprint_gbk.log", "w", encoding="gbk") as f:
        safeprint("Hello World!", "你好世界!", "ハローワールド", "हैलो वर्ल्ड", file=f)
    with open("safeprint_utf8.log", "w", encoding="utf-8") as f:
        safeprint("Hello World!", "你好世界!", "ハローワールド", "हैलो वर्ल्ड", file=f)

此方法使自定义函数 safeprint() 的行为类似于本机 print(),但存在以下差异:

  1. 有一个额外的 errors 参数来确定如何处理编解码器错误(默认为 backslashreplace)。

  2. sepend 参数接受 str 或 None 以外的类型。

safeprint() 检查本机 print() 应该写入的输出文件的编码,并预先对所有文本参数进行编码,因此所有可打印字符都按原样打印,所有不可打印字符打印为已转换。

尽管对所有正在打印的文本进行预先编码和解码似乎效率不高,但原生 encode()decode() 是基于 C 的,并且 运行 非常快。在一次测试中,我在 utf8 控制台中打印了一些带有 utf8 兼容纯文本的文章 5000 次,本机 print() 占用 0:00:02.366799safeprint() 占用 0:00:02.915871。证明性能下降几乎可以忽略不计

以上脚本可以保存为模块脚本,比如safeprint.py。其他脚本可以使用 from safeprint import safeprintsafeprint(),甚至可以使用 from safeprint import safeprint as print 覆盖原生 print(),这样 print() 就可以像 safeprint() 一样工作] 确实如此。