##language:zh #pragma section-numbers on ::-- limodou [<>] <> '''[翻译]Unicode HOWTO''' '''by liqust at gmail dot com http://www.2pole.com/ -- 09/24/2005''' 原文在[[http://www.amk.ca/python/howto/unicode|www.amk.ca/python/howto/unicode]] = Unicode HOWTO = '''Version 1.02''' 这个HOWTO论述了Python对Unicode的支持,并对处理Unicode时经常遇到的各种问题作出了说明。 == Unicode介绍 == === 字符编码历史 === 1968年,American Standard Code for Information Interchange(ASCII)标准被确立。ASCII为各种字符定义了数字编码,范围是0-127。例如,小写字母'a'的编码值为97。 ASCII是一个美国开发的标准,因此并没有包括重音字符。其定义了'e',而没有定义'é'或 'Í'。这意味着包含重音字符的语言不能准确地用ASCII表示(实际上是那些被英语舍弃的重音部分,包括'naïve'和'café'等词语,) 曾有一段时间,人们编写的程序还不能显示重音符号。我记得80年代中期时,针对法语发行的Apple ][ BASIC程序,有类似以下的语句: {{{#!python PRINT "FICHER EST COMPLETE." PRINT "CARACTERE NON ACCEPTE." }}} 这些消息本应该包含重音符,因而对于读懂法语的人来说,产生了错误。 在80年代,几乎所有的个人电脑都是8位的,这意味着可表示0-255的值。ASCII码仅用到127,所以某些机型将128-255的值赋给重音字符。但是,不同的机型使用不同的编码,这样在交换文件时就会产生问题。最终出现了各种普遍使用的128-255字符集。其中某些是国际标准组织(ISO)定义的正规标准,某些是某个公司开发并流行成为事实上的标准。 255个字符并不多。例如,你不能将西欧使用的重音字符和俄罗斯使用的Cyrillic字符同时影射到128-255的范围中,因为它们已经超过了127个字符。 你不能在同同文件中使用不同的编码(所有的俄语文件使用KOI8编码系统,所有的法语文件使用另一种Latin编码系统),但是如果想在一个法语文档中引用某些俄语文本呢?在80年代,人们便准备解决这个问题了,并开始开发UNICODE标准。 Unicode用16比特代替8比特。16比特意味着有2^16 = 65,536个不同的有效值,就有可能同时表示很多字符集的不同字符;一个初始目标是让Unicode包括人类所有语言的字符集。但最终表明即使是16位也是不够的,因此现在的Unicode规范使用更宽的编码范围0-1,114,111 (0x10ffff,16进制 )。 还有个相关的ISO标准,ISO 10646。Unicode和ISO 10646是最初的两个不同的努力方向,但在Unicode的1.1版本时这两个规范就合并在一起了。 (这个关于Unicode历史的论述是被高度简化了的。我认为一般的Python程序员并不需要担心这些历史细节;在参考里列出的Unicode consortium网站上有更多的参考信息) === 定义 === 一个字符可能是文本的最小单位了。如'A','B','C'等,都是不同的字符,而 È ' 和 ''Í '也是。字符是抽象的,并依赖于你使用的语言和上下文环境。例如,ohms符号(Ω )通常都很像希腊字母表中的大写字母omega(Ω)(在某些字体中它们甚至完全一样),但是它们是不同的字符,表示的是不同的意思。 Unicode标准描述了字符是如何通过code point表示的。一个code point就是一个整数值,通常用16进制表示。在标准中,一个code point使用U+12ca来表示值0x12ca(十进制为4810)。Unicode标准包含了很多表格,其中列出了字符和它们对应的code point。 {{{#!python 0061 'a'; LATIN SMALL LETTER A 0062 'b'; LATIN SMALL LETTER B 0063 'c'; LATIN SMALL LETTER C ... 007B '{'; LEFT CURLY BRACKET }}} 严格说来,这些定义表明,说“这是字符U+12ca”是无意义的。U+12ca是个code point,表示的是某个特定的字符,这里,它表示的是 'ETHIOPIC SYLLABLE WI'。在非正规的上下文中,有时code point和字符的区别会被忽略。 一个字符通过一系列被称为glyph的图形元素表示在屏幕或纸张上。对于大写字母A,glyph是两个斜线和一个水平线,但精确的细节还依赖于使用的字体。多数Python代码并不需要担心glyph;绘出正确的glyph并显示通常是某个GUI工具集或某个终端的字体描绘的工作。 === 编码 === 总结前面的部分:一个Unicode字符串是一个code point序列,值从0到0x10ffff。这个序列需要被表示成内存中的一系列字节(即,值从0到255)。将一个Unicode字符串翻译成一个字节序列被称为'''编码'''。 你首先想到的编码可能是一个32比特的整数数组。在这个表示法中,字符串"Python"看起来像这样: {{{#!python P y t h o n 0x50 00 00 00 79 00 00 00 74 00 00 00 68 00 00 00 6f 00 00 00 6e 00 00 00 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 }}} 这个表示法虽然直接却产生了一些问题: 1. 不可移植;不同类型的处理器的字节顺序不同 1. 空间浪费严重。在多数文本中,主要的code point都小于127,或小于255,因此很多的空间被零占据。上面的那个字符串用了24字节,而ASCII只需要6字节。更多的空间使用并不是很要紧(桌面电脑有数以兆计的内存,且字符串通常都不大),但磁盘空间和网络带宽的占用被扩展了四倍则是无法忍受的。 1. 与现有的C函数,如strlen(),不兼容,因此需要使用一系列新的宽字符处理函数。 1. 很多因特网标准使用的数据为文本形式,并不能处理内嵌零字节的内容。 通常,人们并不使用这种编码。而选择其它更有效率、更便利的编码。 编码时并不需要处理每个可能的Unicode字符,且多数时候都是这样。例如,Python的默认编码是'ascii'。将Unicode字符串转换成ASCII编码的规则是很简单的;对于每个code point: 1. 如果code point<128,每个字节的值与code point相同。 1. 如果code point是128或更大,Unicode字符串不能被表示(在这种情况下Python抛出UnicodeEncodeError异常) Latin-1,也被称为ISO-8859-1,是一个类似的编码。Unicode中0-255的code point与Latin-1一样,所以只需要将code point简单地转换成字节值就可以了;如果code point大于255,字符串则不能转换成Latin-1。 编码并不一定要像Latin一样是一对一的简单影射。参考IBM大型机上使用的IBM的EBCDIC。字母的值并不都在一个块中:'a'到'i'的值从129到137,但'j'到'r'的值是从145到153。如果你想使用EBCDIC来编码,很有可能会使用某种类型的查询表来完成转换,但这通常是一个内部问题。 UTF-8是一个广泛使用的编码方案。UTF 代表"Unicode Transformation Format",'8'意味着编码时使用8比特。(还有一种UTF-16编码,但其使用没有UTF-8频繁) 1. 如果code point<128,就用对应的字节值表示。 1. 如果code point在128和0x7ff之间,就转换成两个字节,每个字节的值从128到255。 1. 若code point大于0x7ff,就转换成三到四字节,每个字节的值从128到255。 UTF-8有几个优点: 1. 可以处理任何Unicode code point。 1. Unicode字符串被转换后没有内嵌的零字节。这就避免了字节顺序的问题,并意味着UTF-8字符串可以用strcpy()等C函数处理,并能适用于不能处理零字节的协议。 1. 一个ASCII字符串也是有效的UTF-8字符串。 1. UTF-8相当紧凑,绝大部分code point被转换成两个字节,而值小于128的只占一个字节。 1. 如果有字节被破坏或丢失,也可判断出下一个UTF-8 code point的位置并重新同步。同样,一个随机的8比特数据也不大可能被认为是有效的UTF-8数据。 === 参考 === The Unicode Consortium site at <[[http://www.unicode.org|http://www.unicode.org]]> has character charts, a glossary, and PDF versions of the Unicode specification. Be prepared for some difficult reading. <[[http://www.unicode.org/history/|http://www.unicode.org/history/]]> is a chronology of the origin and development of Unicode. To help understand the standard, Jukka Korpela has written an introductory guide to reading the Unicode character tables, available at <[[http://www.cs.tut.fi/~jkorpela/unicode/guide.html|http://www.cs.tut.fi/~jkorpela/unicode/guide.html]]>. Roman Czyborra wrote another explanation of Unicode's basic principles; it's at <[[http://czyborra.com/unicode/characters.html|http://czyborra.com/unicode/characters.html]]>. Czyborra has written a number of other Unicode-related documentation, available from <[[http://www.cyzborra.com|http://www.cyzborra.com]]>. Two other good introductory articles were written by Joel Spolsky <[[http://www.joelonsoftware.com/articles/Unicode.html|http://www.joelonsoftware.com/articles/Unicode.html]]> and Jason Orendorff <[[http://www.jorendorff.com/articles/unicode/|http://www.jorendorff.com/articles/unicode/]]>. If this introduction didn't make things clear to you, you should try reading one of these alternate articles before continuing. Wikipedia entries are often helpful; see the entries for "character encoding" <[[http://en.wikipedia.org/wiki/Character_encoding|http://en.wikipedia.org/wiki/Character_encoding]]> and UTF-8 <[[http://en.wikipedia.org/wiki/UTF-8|http://en.wikipedia.org/wiki/UTF-8]]>, for example. == Python的Unicode支持 == 现在你已经了解了Unicode的基础知识,我们可以关注Python的Unicode特性了。 === Unicode类型 === Unicode字符串用Python的unicode类型的实例表示,unicode类型是Python的一个内建类型。它来源于一个被称之为basestring的抽象类型,这个类型也是str类型的祖先;你可以用isinstance(value, basestring)检查某个值是否是字符串类型。在内部,Python用16或32字节来表示Unicode字符串,这取决于Python解释器是如何被编译的,但构造函数unicode()的用法为unicode(string[, encoding, errors])。所有参数都应是8比特的字符串。使用指定的encoding参数将第一个参数转换成Unicode字符串;如果不指定encoding参数,便使用ASCII编码,因而值大于127的字符会被认为是错误: {{{#!python >>> unicode('abcdef') u'abcdef' >>> s = unicode('abcdef') >>> type(s) >>> unicode('abcdef' + chr(255)) Traceback (most recent call last): File "", line 1, in ? UnicodeDecodeError: 'ascii' codec can't decode byte 0xff in position 6: ordinal not in range(128) }}} 当输入字符串依照encoding的规则却不能被转换时,参数errors指定了返回信息。这个参数的合法值有'strict' (抛出一个 UnicodeDecodeError 异常), 'replace' ( U+FFFD, '被替换的字符'), 或 'ignore' (仅使错误字符不出现在结果中): {{{#!python >>> unicode('\x80abc', errors='strict') Traceback (most recent call last): File "", line 1, in ? UnicodeDecodeError: 'ascii' codec can't decode byte 0x80 in position 0: ordinal not in range(128) >>> unicode('\x80abc', errors='replace') u'\ufffdabc' >>> unicode('\x80abc', errors='ignore') u'abc' }}} 用一个包含编码名称的字符串指定encoding。Pyrhon 2.4大约有100种不同的encoding;在[[http://docs.python.org/lib/standard-encodings.html|http://docs.python.org/lib/standard-encodings.html]]> 查看Python库参考中的列表。有些encoding有好几个名称;例如, 'latin-1', 'iso_8859_1' 和 '8859' 都是同一个编码。 只有一个字符的Unicode字符串还可以使用unichr()内建函数,它接收整数并返回一个长度为1的包含相应code point的Unicode字符串。使用ord()内建函数可完成逆反操作,它接受一个长度为1的Unicode字符串并返回code point值: {{{#!python >>> unichr(40960) u'\ua000' >>> ord(u'\ua000') 40960 }}} 8比特字符串类型能的很多方法,unicode类型也能提供,比如搜索和格式化操作: {{{#!python >>> s = u'Was ever feather so lightly blown to and fro as this multitude?' >>> s.count('e') 5 >>> s.find('feather') 9 >>> s.find('bird') -1 >>> s.replace('feather', 'sand') u'Was ever sand so lightly blown to and fro as this multitude?' >>> s.upper() u'WAS EVER FEATHER SO LIGHTLY BLOWN TO AND FRO AS THIS MULTITUDE?' }}} 注意这些方法的参数可以是Unicode字符串或8比特字符串。在执行这些操作时8比特字符串被转换成Unicode字符串;并会使用Python的默认ASCII编码,因而值大于127的字符会产生异常: {{{#!python >>> s.find('Was\x9f') Traceback (most recent call last): File "", line 1, in ? UnicodeDecodeError: 'ascii' codec can't decode byte 0x9f in position 3: ordinal not in range(128) >>> s.find(u'Was\x9f') -1 }}} 因此很多操作字符串的Python代码并不需要做任何改动就可以操作Unicode字符串。(为操作Unicode,输入和输出代码需要更多的改进,后面会有更多的介绍) 另一个重要的方法是 .encode([encoding], [errors='strict']),它返回一个Unicode字符串的8比特字符串版本,使用的是encoding参数。errors参数用法与Unicode()构造函数一样,但多了一个选择;不仅有 'strict', 'ignore', 和 'replace',还可以传递 'xmlcharrefreplace' ,用来使用XML的字符引用。以下例子展示了不同的结果: {{{#!python >>> u = unichr(40960) + u'abcd' + unichr(1972) >>> u.encode('utf-8') '\xea\x80\x80abcd\xde\xb4' >>> u.encode('ascii') Traceback (most recent call last): File "", line 1, in ? UnicodeEncodeError: 'ascii' codec can't encode character '\ua000' in position 0: ordinal not in range(128) >>> u.encode('ascii', 'ignore') 'abcd' >>> u.encode('ascii', 'replace') '?abcd?' >>> u.encode('ascii', 'xmlcharrefreplace') 'ꀀabcd޴' }}} Python的8比特字符串有一个.decode([encoding], [errors]) 方法,使用给定的encoding解释字符串: {{{#!python >>> u = unichr(40960) + u'abcd' + unichr(1972) # Assemble a string >>> utf8_version = u.encode('utf-8') # Encode as UTF-8 >>> type(utf8_version), utf8_version (, '\xea\x80\x80abcd\xde\xb4') >>> u2 = utf8_version.decode('utf-8') # Decode using UTF-8 >>> u == u2 # The two strings match True }}} 注册和访问有效的encoding的底层路径可以在codecs模块中找到。但是,这个模块返回的encoding和decoding函数通常都过于底层,所以我并不准备在这里介绍codecs模块。如果你想实现一个全新的encoding,则需学习codecs模块接口,但实现encoding是一个专门的任务,在这里并不准备介绍。参考Python文档 可以对这个模块了解更多。 codesc模块中最常使用的部分是codecs.open()函数,它将会在输入和输出这节中论述。 === Python代码中的Unicode文本 === 在Python源代码中,Unicode文本用带有'u'或'U'前缀的字符串表示:u'abcdefghijk'。特殊的code point可以使用\u 转义序列 表示,后面的是表示code point的四个十六进制值。 \U 转义序列 则类似,但使用的是8个数字,而不是4个。 Unicode文本也可以使用与8比特字符串一样的转义序列,包括\x,但\x只能用两个数字,因此不能表示任意的code point。八进制的转义可以表示到U+01ff,即八进制的777。 {{{#!python >>> s = u"a\xac\u1234\u20ac\U00008000" ^^^^ two-digit hex escape ^^^^^^ four-digit Unicode escape ^^^^^^^^^^ eight-digit Unicode escape >>> for c in s: print ord(c), ... 97 172 4660 8364 32768 }}} 对大于127的code point使用转义序列 在数量较少时是很好的,但如果有大量的重音字符则会令人厌烦,比如程序中的消息为法语或其它使用重音的语言。你可以使用unichr()内建函数,但这甚至更令人厌烦。 理想状况下,你可以使用自己语言的自然编码来书写文本。然后选择支持重音字符显示的编辑器编写Python代码,并在运行时得到正确的字符。 Python支持任何编码的Unicode文本,但是你需要声明所使用的编码。在源文件的第一或第二行包含一个特殊的注释就可做到这点: {{{#!python #!/usr/bin/env python # -*- coding: latin-1 -*- u = u'abcdé' print ord(u[-1]) }}} 这种语法格式受到Emacs指定本地文件变量想法的启发。Emacs支持很多不同的变量,但是Python只支持'coding'。-*- symbol 表明这是个特殊注释;在其中你必须提供名称coding和你选择的编码,并用':'分开。 如果不包含这条注释,则会使用默认编码ASCII。在2.4版本之前的Python是以欧洲为中心的,并假定字符串文本的默认编码为Latin-1,在Python 2.4 版本中,大于127的字符也能工作但会返回一个警告。例如,以下程序没有编码声明: {{{#!python #!/usr/bin/env python u = u'abcdé' print ord(u[-1]) }}} 当你用Python 2.4 版本运行它时,会输出以下警告: {{{#!python amk:~$ python p263.py sys:1: DeprecationWarning: Non-ASCII character '\xe9' in file p263.py on line 2, but no encoding declared; see http://www.python.org/peps/pep-0263.html for details }}} === Unicode属性 === Unicode规范包含了一个存储code point信息的数据库。这些信息包括了每个定义的code point的字符名称,所属类别,如果可能的话还有数字信息(在Unicode中的某些字符,代表罗马数字和例如三分之一和五分之四的分数)。同时还包含了使用双向文本时的信息和其它的关于显示的信息。 以下程序显示了若干字符的部分信息,并打印出特定字符的数值: {{{#!python import unicodedata u = unichr(233) + unichr(0x0bf2) + unichr(3972) + unichr(6000) + unichr(13231) for i, c in enumerate(u): print i, '%04x' % ord(c), unicodedata.category(c), print unicodedata.name(c) # Get numeric value of second character print unicodedata.numeric(u[1]) }}} 运行时,打印出: {{{#!python 0 00e9 Ll LATIN SMALL LETTER E WITH ACUTE 1 0bf2 No TAMIL NUMBER ONE THOUSAND 2 0f84 Mn TIBETAN MARK HALANTA 3 1770 Lo TAGBANWA LETTER SA 4 33af So SQUARE RAD OVER S SQUARED 1000.0 }}} 类别码表示字符所属的类别。字符被划分为若干类别,如"Letter", "Number", "Punctuation", 或 "Symbol",而它们又有子类别。在以上的输出中,'Ll' 代表 'Letter, lowercase', 'No' 代表 "Number, other", 'Mn' 为 "Mark, nonspacing", 'So' 为 "Symbol, other".。参考<[[http://www.unicode.org/Public/UNIDATA/UCD.html#General_Category_Values|http://www.unicode.org/Public/UNIDATA/UCD.html#General_Category_Values]]>上的类别列表。 === 参考 === The Unicode and 8-bit string types are described in the Python library reference at <[[http://docs.python.org/lib/typesseq.html|http://docs.python.org/lib/typesseq.html]]>. The documentation for the unicodedata module is at <[[http://docs.python.org/lib/module-unicodedata.html|http://docs.python.org/lib/module-unicodedata.html]]>. The documentation for the codecs module is at <[[http://docs.python.org/lib/module-codecs.html|http://docs.python.org/lib/module-codecs.html]]>. Marc-André Lemburg gave a presentation at EuroPython 2002 titled "Python and Unicode". A PDF version of his slides is available at <[[http://www.egenix.com/files/python/Unicode-EPC2002-Talk.pdf|http://www.egenix.com/files/python/Unicode-EPC2002-Talk.pdf]]>, and is an excellent overview of the design of Python's Unicode features. == 读写Unicode数据 == 当你编写了可处理Unicode数据的代码后,下一个问题就是输入/输出。如何取得Unicode字符串,又如何将Unicode转换成适合存储或传输的格式? 取决于你的输入来源和输出目标,你也有可能并不需要做任何事;你应当检查程序使用的库是否直接支持Unicode。例如,XML解析器经常返回Unicode数据。很多关系数据库支持Unicode值的字段,并能对SQL查询返回Unicode值。 Unicode数据通常在写入磁盘或网络传输前被转换成特定的编码。你也可能自己完成所有的任务,打开文件,从中读取8比特字符串,并用unicode(str,encoding)转换它。但是,并不建议使用这种手工方式。 一个问题就是编码可能是多字节的;一个Unicode字符可能用若干个字节表示。如果你想读取任意大小chunk(如,1K或4K)的文件,则需要编写错误处理代码,用来处理在chunk结尾时哪几个字节表示一个Unicode字符的情况。一个解决方法是一次性地读取整个文件到内存并解码,但并不适合处理大文件的情况;如果你想读取一个2GB的文件,则需要2GB的内存。(其实更多,至少在某些时候内存中要同时存储解码后的字符串还有它的Unicode版本) 解决方法应该是使用底层的解码接口,处理部分编码序列的情况。且已经有实现方法了:codecs模块包含了open()函数的一个版本,它可以返回一个类似文件的对象,并保证文件的内容是由特定的encoding编码,而它的method如.read()和.write()还能接收Unicode参数。 函数的参数使用方式为open(filename, mode='rb', encoding=None, errors='strict', buffering=1)。mode可以为'r','w',或'a',就像内建函数open()对应的参数一样;添加'+'则更新文件。类似的,buffering也与标准函数相同。encoding为一个字符串,给出了要使用的encoding;如果没有这个参数,则返回一个接收8比特字符串的常规Python文件。否则,返回一个封装对象,并且对封装对象的读和写操作的数据都会相应的被转换。errors指定在编码错误时的反应,可设置成'strict','ignore'或'replace'。 这样从一个文件读取Unicode就简单了: {{{#!python import codecs f = codecs.open('unicode.rst', encoding='utf-8') for line in f: print repr(line) }}} 也有可能以更新模式打开文件,允许读和写: {{{#!python f = codecs.open('test', encoding='utf-8', mode='w+') f.write(u'\u4500 blah blah blah\n') f.seek(0) print repr(f.readline()[:1]) f.close() }}} Unicode字符U+FEFF被用作字节顺序标记(BOM),并通常作为文件的第一个字符,以便于自动检测文件的字节顺序。某些编码,如UTF-16,则希望文件的开头有一个BOM;当使用这种编码时,这个BOM将作为第一个字符被写入,并在读取文件内容时被去掉。还有若干种编码,如分别针对little endian和big-endian的'utf-16-le'和'utf-16-be',会指定一个特定的字节顺序,却并不跳过这个BOM。 === Unicode文件名 === 大多数流行的操作系统都支持文件名中包含任意的Unicode字符。这通常是通过将Unicode字符串转化成对应的系统编码来实现的。例如,MacOS X用户使用UTF-8,而Windows用户使用一个可配置的编码;在Windows系统上,Python用户使用"mbcs"来获取系统目前的编码。在Unix系统上,如果你设定了LANG或LC CTYPE环境变量,那么只有一种编码;如果没有设定,默认编码为ASCII。 若你想手动设置编码,可使用函数sys.getfilesystemencoding()返回当前使用系统的编码,但通常并不需要去改变它。当打开文件时,通常只需要提供Unicode字符串作为文件名,它会自动转化成相应的编码: {{{#!python filename = u'filename\u4500abc' f = open(filename, 'w') f.write('blah\n') f.close() }}} os模块中的函数如os.stat()也能接收Unicode文件名。 os.listdir()返回文件名,产生一个问题:是返回文件名的Unicode版本呢,还是8比特字符串版本?这两种结果os.listdir()都可以提供,它取决于你提供的目录参数是Unicode字符串还是8比特的字符串。如果你传递一个Unicode字符串作为路径,则会使用本地编码将文件名转换成Unicode字符串并返回,而传递一个8比特字符串路径则会返回8比特字符串文件名。例如,假设默认的本地编码是UTF-8,运行以下程序: {{{#!python fn = u'filename\u4500abc' f = open(fn, 'w') f.close() import os print os.listdir('.') print os.listdir(u'.') }}} will produce the following output: {{{#!python amk:~$ python t.py ['.svn', 'filename\xe4\x94\x80abc', ...] [u'.svn', u'filename\u4500abc', ...] }}} 第一个列表包含了UTF-8文件名,而第二个列表包含的则是Unicode版本。 === 关于编写Unicode-aware程序的技巧 === 本节提供了一些关于编写能处理Unicode的程序的建议 最重要的技巧就是: 软件应当只在内部使用Unicode,在输出时再转换成特定的编码。 如果希望编写的程序既能处理Unicode,也能处理8比特字符串,你会发现将两种不同字符串合并起来很容易导致bug。Python的默认编码是ASCII,因此若在输入的数据中遇到ASCII值大于127的字符,就会产生一个UnicodeDecodeError错误,因为ASCII编码不能处理这个字符。 如果你测试软件时提供的数据不包含任何重音字符,则很容易就忽略了这些问题;看起来一切正常,但在程序等待一个意图输入重音字符的用户时,就会遇到一个bug。因此,第二个技巧就是: 在你的测试数据中包含大于127的字符,若大于255则更好。 当使用从浏览器或其它不可靠地方来的数据时,一个通用的方法就是,在命令行中使用或将存储至数据库之前检查字符是否合法。当字符串为使用或存储的格式时要小心检查它 ;存在使用编码对字符进行伪装的可能性。这在输入数据指定了encoding时尤为突出,很多编码并不改变那些被检查的字符,但是Python中有些编码,如'base64',可修改每个单独的字符。 例如,你有一个接受Unicode文件名的内容管理系统,并拒绝含有字符'/'的路径。你可能会编写如下代码: {{{#!python def read_file (filename, encoding): if '/' in filename: raise ValueError("'/' not allowed in filenames") unicode_name = filename.decode(encoding) f = open(unicode_name, 'r') # ... return contents of file ... }}} 但是,如果某位攻击者可以指定'base64'编码,则会传递'L2V0Yy9wYXNzd2Q=',它是字符串'/etc/passwd'的base-64编码形式。以上代码在这个字符串中查找字符'/',却找不到这个危险的字符。 === 参考 === The PDF slides for Marc-André Lemburg's presentation "Writing Unicode-aware Applications in Python" are available at <[[http://www.egenix.com/files/python/LSM2005-Developing-Unicode-aware-applications-in-Python.pdf|http://www.egenix.com/files/python/LSM2005-Developing-Unicode-aware-applications-in-Python.pdf]]> and discuss questions of character encodings as well as how to internationalize and localize an application. == Revision History and Acknowledgements == Thanks to the following people who have noted errors or offered suggestions on this article: Nicholas Bastin, Marius Gedminas, Kent Johnson, Ken Krugler, Marc-André Lemburg, Martin von L??wis. Version 1.0: posted August 5 2005. Version 1.01: posted August 7 2005. Corrects factual and markup errors; adds several links. Version 1.02: posted August 16 2005. Corrects factual errors. ---- Email:liqust '''at''' gmail dot com ©2005 '''2pole'''