jfo*_*erg 20 python unicode cgi python-3.x
在编写Python 3.1 CGI脚本时,我遇到了可怕的UnicodeDecodeErrors.但是,在命令行上运行脚本时,一切正常.
看来open()并print()使用返回值locale.getpreferredencoding()来了解默认使用的编码.在命令行上运行时,该值应为"UTF-8".但是当通过浏览器运行脚本时,神秘地将编码重新定义为"ANSI_X3.4-1968",这似乎只是普通ASCII的一个奇特名称.
我现在需要知道如何在所有情况下使用'utf-8'作为默认编码来运行cgi脚本.我的设置是Debian Linux上的Python 3.1.3和Apache2.系统范围的语言环境是en_GB.utf-8.
小智 17
为后来者回答这个问题,因为我不认为发布的答案会找到问题的根源,即CGI上下文中缺少语言环境变量.我正在使用Python 3.2.
open()以文本(字符串)或二进制(字节)模式打开文件对象以进行读取和/或写入; 在文本模式中,用于编码写入文件的字符串的编码,以及解码从文件读取的字节,可以在调用中指定; 如果不是那么它由locale.getpreferredencoding()决定,它在linux上使用你的语言环境设置的编码,通常是utf-8(来自例如LANG = en_US.UTF-8)
>>> f = open('foo', 'w') # open file for writing in text mode
>>> f.encoding
'UTF-8' # encoding is from the environment
>>> f.write('€') # write a Unicode string
1
>>> f.close()
>>> exit()
user@host:~$ hd foo
00000000 e2 82 ac |...| # data is UTF-8 encoded
Run Code Online (Sandbox Code Playgroud)sys.stdout实际上是一个文件打开文件模式,其编码基于locale.getpreferredencoding(); 你可以很好地编写字符串,它们将根据sys.stdout的编码被编码为字节; print()默认写入sys.stdout - print()本身没有编码,而是它写入的文件有编码;
>>> sys.stdout.encoding
'UTF-8' # encoding is from the environment
>>> exit()
user@host:~$ python3 -c 'print("€")' > foo
user@host:~$ hd foo
00000000 e2 82 ac 0a |....| # data is UTF-8 encoded; \n is from print()
Run Code Online (Sandbox Code Playgroud)
; 你不能写字节到sys.stdout - 使用sys.stdout.buffer.write(); 如果您尝试使用sys.stdout.write()将字节写入sys.stdout然后它将返回错误,如果您尝试使用print(),则print()将简单地将bytes对象转换为字符串对象和转义序列\xff将被视为四个字符\,x,f,f
user@host:~$ python3 -c 'print(b"\xe2\xf82\xac")' > foo
user@host:~$ hd foo
00000000 62 27 5c 78 65 32 5c 78 66 38 32 5c 78 61 63 27 |b'\xe2\xf82\xac'|
00000010 0a |.|
Run Code Online (Sandbox Code Playgroud)在CGI脚本中,您需要写入sys.stdout,您可以使用print()来执行此操作; 但是Apache中的CGI脚本进程没有语言环境设置 - 它们不是CGI规范的一部分; 因此sys.stdout编码默认为ANSI_X3.4-1968 - 换句话说,ASCII; 如果你尝试print()一个包含非ASCII字符的字符串到sys.stdout你会得到"UnicodeEncodeError:'ascii'编解码器不能编码字符...:ordinal不在范围内(128)"
一个简单的解决方案是使用服务器或虚拟主机配置中的Apache的mod_env PassEnv命令将Apache进程的LANG环境变量传递给CGI脚本:PassEnv LANG; 在Debian/Ubuntu上确保你在/ etc/apache2/envvars中取消注释"./ etc/default/locale"行,以便Apache运行系统默认语言环境而不是C(Posix)语言环境(也是ASCII)编码); 以下CGI脚本应该在Python 3.2中正确运行:
#!/usr/bin/env python3
import sys
print('Content-Type: text/html; charset=utf-8')
print()
print('<html><body><pre>' + sys.stdout.encoding + '</pre>h€lló wörld<body></html>')
Run Code Online (Sandbox Code Playgroud)
您不应该将 IO 流作为 CGI/WSGI 的字符串来读取;它们不是 Unicode 字符串,而是明确的字节序列。
(考虑Content-Length以字节而不是字符来衡量;想象一下尝试读取multipart/form-data压缩为 UTF-8 解码字符串的二进制文件上传提交,或返回二进制文件下载...)
因此,请使用sys.stdin.buffer和sys.stdout.buffer来获取 stdio 的原始字节流,并用它们读/写二进制文件。由表单读取层在适当的情况下使用网页所采用的编码将这些字节转换为 Unicode 字符串参数。
不幸的是,标准库 CGI 和 WSGI 接口在 Python 3.1 中并没有做到这一点:相关模块是从 Python 2 原始版本粗略转换而来的2to3,因此存在许多错误,最终会导致 UnicodeError。
可用于 Web 应用程序的第一个 Python 3 版本是 3.2。使用 3.0/3.1 几乎是浪费时间。令人遗憾的是,花了很长时间才解决这个问题,PEP3333 通过了。