为什么在Python中要通过字符串声明Unicode?

123

我仍在学习python,有一个疑问:

在python 2.6.x中,我通常会像这样在文件头部声明编码(如PEP 0263中所述)

# -*- coding: utf-8 -*-

然后,我的字符串就像平常一样被写入:

a = "A normal string without declared Unicode"

但是每次我看到 Python 项目代码时,都没有在头部声明编码方式。相反,它是在每个字符串中声明的,就像这样:

a = u"A string with declared Unicode"

有什么区别?这样做的目的是什么?我知道Python 2.6.x默认设置ASCII编码,但可以通过头声明覆盖,那么每个字符串声明的意义是什么?

附言:看来我混淆了文件编码和字符串编码。谢谢解释 :)


6
“# coding: utf8” 已足够,无需使用“-*-”。 - jellyfish
1
@jellyfish 我猜你是想输入 # coding: utf-8 - Samuel Harmer
应该是 #coding=utf-8。https://www.python.org/dev/peps/pep-0263/ - Guangtong Shen
5个回答

168
那是两件不同的事情,正如其他人所提到的。
当您指定# -*- coding: utf-8 -*-时,您告诉Python保存的源文件是utf-8。Python 2的默认值为ASCII(Python 3为utf-8)。这只影响解释器读取文件中的字符的方式。
一般来说,无论编码方式是什么,将高Unicode字符嵌入文件可能都不是最佳选择;您可以使用字符串Unicode转义,它适用于任何编码方式。
当您在字符串前面加上u(例如u'This is a string')时,它告诉Python编译器该字符串是Unicode,而不是字节。解释器几乎完全透明地处理这个问题;最明显的区别是现在可以在字符串中嵌入Unicode字符(也就是说,u'\u2665' 现在是合法的)。 您可以使用from __future__ import unicode_literals使其成为默认设置。
这仅适用于Python 2;在Python 3中,Unicode是默认值,您需要在前面指定b(例如b'These are bytes'),以声明一个字节序列。

2
Python 2 的默认源编码是 ascii - Mark Tolonen
28
将高Unicode字符嵌入您的文件中实际上是一个很好的想法。我怀疑非英语为母语的人不想在他们的字符串中阅读Unicode转义字符。 - Mark Tolonen
4
如果你正在编写国际化应用程序,这个说法是正确的,但请考虑一下如果你是中国或法国程序员。需要注意的不仅是字符串,还包括注释。幸运的是,Python在源编码方面非常灵活,Python 3甚至可以在变量名中包含非ASCII字符。 - Mark Tolonen
1
“-*-” 是 Emacs 语法。显然,Emacs 实现了这种指定编码的方式,Python 与其语法一起使用了这个想法。 - anatolyg
1
@LeiYang Python不需要使用“-*-”语法。最少可以使用#coding=utf8,或者更精确地说,一行代码匹配正则表达式^[ \t\f]*#.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+)即可。 - Mark Tolonen
显示剩余3条评论

24

正如其他人所说,# coding:指定源文件保存的编码方式。以下是一些例子以说明这一点:

一个以cp437(我的控制台编码)保存在磁盘上的文件,但没有声明编码方式

b = 'über'
u = u'über'
print b,repr(b)
print u,repr(u)

输出:

  File "C:\ex.py", line 1
SyntaxError: Non-ASCII character '\x81' in file C:\ex.py on line 1, but no
encoding declared; see http://www.python.org/peps/pep-0263.html for details

加上 # coding: cp437 后文件的输出结果:

über '\x81ber'
über u'\xfcber'
起初,Python不知道该编码方式,因此会抱怨非ASCII字符。一旦它了解了编码方式,字节字符串就获得了实际上磁盘中包含的字节。对于Unicode字符串,Python读取\x81,并知道在cp437中这代表ü,然后将其解码为Unicode代码点ü(即U+00FC)。当打印字节字符串时,Python直接向控制台发送十六进制值81。当打印Unicode字符串时,Python正确检测到我的控制台编码为cp437,并将Unicodeü转换为cp437中ü的值。

下面介绍了一个声明并以UTF-8保存的文件会发生什么:

├╝ber '\xc3\xbcber'
über u'\xfcber'

在 UTF-8 编码中,ü 被编码为十六进制字节 C3 BC,所以字节字符串包含这些字节,但 Unicode 字符串与第一个示例相同。Python 读取了这两个字节并且正确解码了它。Python 打印了字节字符串时出现了错误,因为它直接将代表ü的两个 UTF-8 字节发送到我的 cp437 控制台。

这里的文件声明为 cp437,但保存为 UTF-8 格式:

├╝ber '\xc3\xbcber'
├╝ber u'\u251c\u255dber'

这个字节字符串仍然保留着存储在磁盘中的字节(UTF-8十六进制字节C3 BC),但是将它们解释为两个cp437字符而不是单个UTF-8编码的字符。 这两个字符被转换为Unicode代码点,导致所有内容打印出来都是不正确的。


10

这并不是设置字符串的格式,而是设置文件的格式。即使有了这个头部信息,"hello" 仍然是一个字节字符串,而不是 Unicode 字符串。如果要使用 Unicode 字符串,你需要在所有地方使用 u"hello"。该头部信息只是指示在读取 .py 文件时使用的格式。


当时我误解了,我以为它们是一样的。因此使用Unicode字符串的目的是国际化(i18n)? - CastleDweller
@Oscar:大部分是的。如果你正在使用Django或类似的工具制作一个网站,并且需要处理包含非ASCII字符的用户,则这也是可能的用例之一。 - icktoofay

7
标签的定义是用来定义代码本身的编码,而不是运行时结果字符串的编码。

如果在Python脚本中加入像۲这样的非ASCII字符且没有使用utf-8头文件定义,则会引发警告。

错误


-1

我创建了一个名为“unicoder”的模块,以便能够在变量上进行转换:

import sys
import os

def ustr(string):

    string = 'u"%s"'%string

    with open('_unicoder.py', 'w') as script:

        script.write('# -*- coding: utf-8 -*-\n')
        script.write('_ustr = %s'%string)

    import _unicoder
    value = _unicoder._ustr

    del _unicoder
    del sys.modules['_unicoder']

    os.system('del _unicoder.py')
    os.system('del _unicoder.pyc')

    return value

然后在你的程序中,你可以这样做:

# -*- coding: utf-8 -*-

from unicoder import ustr

txt = 'Hello, Unicode World'
txt = ustr(txt)

print type(txt) # <type 'unicode'>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接