今天我对Python 3进行了一次小小的冒险。我有一个程序,它接受标准输入,在写出来之前读取并简单地处理了一堆头信息,然后只是把正文(电子邮件,因为它发生了)从标准输入复制到标准输出。通常情况下,它得到的是良好的输入,没有非法编码的UTF-8。今天,有一些杂散的字节,世界爆炸了。 处理这个问题比它应该有的要难得多,部分原因是文档有问题。
尽管sys.stdin的文档不会告诉你这些,sys.stdin 很可能有io.TextIOBaseWrapper 的 API。否则,你要想知道它支持哪些属性和方法,唯一的方法就是在 Python 解释器中使用永远友好的 'help(type(sys.stdin))'。如果你在 Python 3.7 或更高版本上,你可能想对一个编码不好的标准输入做的事情是改变它处理编码错误的方式,用 .reconfigure():
sys.stdin.reconfigure(errors="surrogateescape")
现在我已经知道了这个问题,我认为在任何从标准输入读取数据的Python 3程序中,你通常应该把这个作为第一个操作,除非你绝对确定输入的不是格式良好的UTF-8是一个致命的错误(几乎从来没有)。
不幸的是,Ubuntu 18.04 LTS将Python 3.6.9作为其/usr/bin/python3,所以我不能这样做。一个选择似乎是在sys.stdin 后面分离出底层的io.BufferedReader,然后用你想要的错误处理方式重新创建它。我相信这将是。
b = sys.stdin.detach()
sys.stdin = io.TextIOWrapper(b, errors="surrogateescape")
你对errors= 的选择在 codecs 模块的错误处理程序文档中有所记载。你可能更喜欢像 "backslashreplace "或 "namereplace "这样的东西,因为它们使输出的UTF-8正确。我是个老派的人,所以我更喜欢把坏字节原封不动地传给别人。
另一个选择是直接使用底层的sys.stdin.buffer对象,而不改变sys.stdin 。这个对象支持所有像.readline() 一样的常规 IO 方法,但它返回的是字节而不是字符串;然后你可以随心所欲地处理这些字节,无论是否用某种形式的错误处理对其进行解码。类似地,sys.stdout.buffer ,为.write() ,需要字节而不是字符串。这意味着将标准输入复制到标准输出的无故障方法是:
sys.stdout.buffer.write( sys.stdin.buffer.read() )
如果你以前写过文本模式sys.stdout ,你需要在用 'sys.stdout.flush()' 开始这个拷贝之前冲洗它。如果你省略了这一点,Python 可能会对你的初始输出做一些奇怪的、无益的事情。
(这在经常使用 Python 的开发者社区中可能都是众所周知的,但这些天我是一个不经常使用 Python 的程序员。)