简介
这篇文章是为了介绍定义python源文件文件编码的方法。python解释器可以根据所指定的编码信息对当前文件进行解析。通常来说,这种方法可以提高解析器对Unicode编码的源文件的识别,并且支持书写Unicode编码,例如在一个支持unicode编码的编辑器中使用UTF-8。
问题
在python2.1中,unicode编码只能通过Latin-1中的“unicode-escape”的方式来实现。这让很多平时不使用Latin-1编码的用户感到非常的不友好,尤其是大多数的亚洲国家更是这样。程序员可以使用他们所习惯的编码来编写8-bit的字符代码,却不得不因为要用到unicode编码而使用"unicode-escape"。
解决方法
我们希望让每一个python文件都可以通过在文件顶端书写一些特定的注释的方法,来实现python源代码编码的可见性和易维护性。
为了让python能够识别出文件编码的定义,需要做一些列关于对python源文件代码数据的扩充。
定义编码
在没有指定其它编码的情况下,python默认使用ASCII作为标准编码。
如果想要定义文件代码编码,一个特殊的注释应该放到源文件的第一或第二行,例如:
# coding=<encoding name>
或(使用一种大多数编辑器都能够识别的方式)
#!/usr/bin/python
# -*- coding: <encoding name> -*-
或
#!/usr/bin/python
# vim: set fileencoding=<encoding name> :
恰好,第一或第二行都完全符合正则表达式“coding[:=]\s*([-\w.]+)”。这个表达式获取到的第一组信息就是编码名称。如果python无法识别编码名称,那么将会在编译完成时报错。在python编码定义的行中,严禁出现python的其它声明。
为了避免一些系统,例如windows,在unicode文件头中增加了文件头标记,UTF-8的签名“\xef\xbb\xbf”同样会作识别文件编码的参照(即使没有设置文件编码注释)。
如果一个源文件既有UTF-8文件头标记,又用注释声明了文件编码,那么此时仅可以声明成“UTF-8”。其它的编码将会导致错误。
示例
以下示例用语说明使用不同的方法,如何在python文件顶端定义python源文件编码。
文件:
1. 利用解释器,并使用Emacs风格的文件编码
注释:
#!/usr/bin/python
# -*- coding: latin-1 -*-
import os, sys
... #!/usr/bin/python
# -*- coding: iso-8859-15 -*-
import os, sys
... #!/usr/bin/python
# -*- coding: ascii -*-
import os, sys
...
2. 不使用解释器,使用一段文字描述
# This Python file uses the following encoding: utf-8
import os, sys
...
3. 文本编辑器中,可以有不同的方式来定义文件的编码,例如:
#!/usr/local/bin/python
# coding: latin-1
import os, sys
...
4. 不使用编码注释,python解释器会将文件当成是ASCII:
#!/usr/local/bin/python
import os, sys
...
5. 不会生效的编码注释:
遗失了“coding:”前缀:
#!/usr/local/bin/python
# latin-1
import os, sys
...
编码注释不在第一和第二行:
#!/usr/local/bin/python
#
# -*- coding: latin-1 -*-
import os, sys
...
不支持的编码:
#!/usr/local/bin/python
# -*- coding: utf-42 -*-
import os, sys
...
理念
我们是根据以下理念来实现的编码注释的用法:
1. 一个python源文件应该具有唯一的编码。内部混杂多种编码数据的行为是不被允许的,并且在编译时会报错。
任意一个能够识别出源码前两行,并且符合以上讨论的编码,都可以作为源码文件的编码,包括ASCII兼容编码和某些多字节编码,如Shift_JIS。所有字符都使用至少两个字节的编码无法被识别,例如UTF-16。这是为了让编码检测算法的检测功能更加简洁。
2. 对于未定义的信息,应该什么都不处理而继续进行分析,就像当前的行为一样。其实所有的可用编码值,都是标准的字符串字符(都是8-bit的Unicode),它们对于其它可能出现在代码中的未定义的信息来说只是很小的一部分。
3. python的 tokenizer/compiler 组件应该被更新为一下的工作流程:
a)读取文件
b)将文件解码成Unicode编码,即一个固定的、假设的编码
c)将文件转换为UTF-8字节字符串
d)tokenize UTF-8化的内容
e)编译,根据给定的Unicode数据创建Unicode对象 ,并且根据文件中给定的编码将UTF-8数据重编码成新的8-bit字符串数据。
注意,python的识别码被限制为了ASCII编码集合,所以在步骤d后不用再对它进行额外的转换。
向下兼容
为了兼容已存在的,没有使用ASCII编码并且没有声明编码格式的,现在需要进行2步来进行使用:
1. 将所有不使用ASCII编码并且没有做注释的文件,将它们当作丢失了“iso-8859-1”定义。这会导致强行将字节字符串的处理放置到步骤2-5之前,并且在python2.2中,提升了对非ASCII字符——Unicode的兼容。
当发现输入文件没有ASCII字节时,会在编码输入文件的时候产生一个警告。
2. 删除警告,并将默认编码格式设置为“ASCII”。
内建compile() API,用来提高对输入文件为Unicode编码时的处理能力。字节字符串的输入的处理方式在上文中已经进行过描述。
如果一个带有编码声明的字符串传递给了compile(),那么将会发生一个SyntaxError。
SUZUKI Hisao可以通过使用patch来兼容,查看[2]获取更多信息。
只有在第一步可以使用的patch,在[1]。
新进展
向下兼容中的步骤1和2已经在2.3版本中进行了完善,除了将默认编码置为“ascii”。
在2.5版本中,实现了将默认编码设置成“ascii”。
链接
[1] Phase 1 implementation:
http://python.org/sf/526840
[2] Phase 2 implementation:
http://python.org/sf/534304
历史
1.10 and above: see CVS history
1.8: Added '.' to the coding RE.
1.7: Added warnings to phase 1 implementation. Replaced the
Latin-1 default encoding with the interpreter's default
encoding. Added tweaks to compile().
1.4 - 1.6: Minor tweaks
1.3: Worked in comments by Martin v. Loewis:
UTF-8 BOM mark detection, Emacs style magic comment,
two phase approach to the implementation
版权
This document has been placed in the public domain.