当语言分支到最新的Python 3.x版本时,Python 2.x中的许多内容并未发生巨大变化。Python字符串不是其中之一,事实上它可能是最彻底改变的。它所经历的变化在Python 3.x中编码/解码中如何处理字符串最明显,而不是Python 2.x. 在Python 2.x中编码和解码字符串有些繁琐,正如您可能在另一篇文章中读到的那样。值得庆幸的是,将8位字符串转换为unicode字符串,反之亦然,并且在Python 3.x中忘记了两者之间的所有方法。让我们直接看一些例子来研究这意味着什么。
我们将从包含非ASCII字符的示例字符串开始(即“ü”或“umlaut-u”):
1 | s = 'Flügel' |
现在,如果我们引用并打印字符串,它会给我们带来基本相同的结果:
1 2 3 4 | >>> s 'Flügel' >>> print(s) Flügel |
与s
Python 2.x中的相同字符串相比,在这种情况下s
已经是Unicode字符串,并且Python 3.x中的所有字符串都是自动Unicode。可见的区别是在我们实例化之后s没有改变。
虽然我们的字符串值包含非ASCII字符,但它与ASCII字符集(即基本拉丁语集)相差不远(实际上它是Basic Latin的补充集的一部分)。如果我们的角色不仅是非ASCII字符而且是非拉丁字符,会发生什么?我们来试试吧:
1 2 3 4 5 | >>> nonlat = '字' >>> nonlat '字' >>> print(nonlat) 字 |
我们可以看到,它是否是包含所有拉丁字符的字符串无关紧要,因为Python 3.x中的字符串都将以这种方式运行(与Python 2.x不同,您可以在IDLE窗口中键入任何字符) !)。
如果您已经在Python 2.x中处理过编码和解码字符串,那么您知道处理它们会更加麻烦,并且Python 3.x使它变得更加痛苦。但是,如果我们不需要使用unicode
,encode
或decode
方法或包括多个反斜杠到我们的字符串变量能够立即使用,那么有什么需要做的,我们有编码或解码我们的Python 3.x的字符串?在回答这个问题之前,我们首先会看看b'...'
Python 3.x中的(字节)对象,而不是Python 2.x中的对象。
Python 3.x字节对象
在Python 2.x中,为字符串文字添加“b”(或“B”)前缀是合法的语法,但它没有什么特别之处:
1 2 | >>> b'prefix in Python 2.x' 'prefix in Python 2.x' |
但是,在Python 3.x中,此前缀表示字符串是一个bytes
与普通字符串不同的对象(我们知道默认情况下是Unicode字符串),甚至保留'b'前缀:
1 2 | >>> b'prefix in Python 3.x' b'prefix in Python 3.x' |
关于字节对象的事情是它们实际上是整数数组,尽管我们将它们视为ASCII字符。它们是整数数组的原因或原因在这一点上对我们来说并不重要,但重要的是我们只将它们看作一串ASCII文字字符,它们只能包含ASCII文字字符。这就是为什么以下内容不起作用(或使用任何非ASCII字符):
1 2 | >>> b'字' SyntaxError: bytes can only contain ASCII literal characters. |
现在看看字节对象如何与字符串相关,让我们首先看看如何将字符串转换为字节对象,反之亦然。
将Python字符串转换为字节,将字节转换为字符串
如果我们想将我们的nonlat
字符串从之前转换为字节对象,我们可以使用bytes
构造函数方法; 但是,如果我们只使用字符串作为唯一参数,我们将收到此错误:
1 2 3 4 | >>> bytes(nonlat) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: string argument without an encoding |
我们可以看到,我们需要包含一个带字符串的编码。让我们使用一个常见的UTF-8编码:
1 2 | >>> bytes(nonlat, 'utf-8') b'\xe5\xad\x97' |
现在我们有了我们的bytes
对象,用UTF-8编码......但究竟是什么意思呢?这意味着我们的nonlat
变量中包含的单个字符被有效地转换为一串代码,这意味着UTF-8中的“字” - 换句话说,它被编码。这是否意味着如果我们使用encode
方法调用nonlat
,我们将获得相同的结果?让我们来看看:
1 2 | >>> nonlat.encode() b'\xe5\xad\x97' |
实际上我们得到了相同的结果,但在这种情况下我们不必给出编码,因为Python 3.x中的encode方法默认使用UTF-8编码。如果我们将其更改为UTF-16,我们会得到不同的结果:
1 2 | >>> nonlat.encode('utf-16') b'\xff\xfeW[' |
尽管两个调用都执行相同的功能,但它们的执行方式略有不同,具体取决于编码或编解码器。
由于我们可以对字符串进行编码以生成字节,因此我们也可以解码字节以生成字符串 - 但是在解码字节对象时,我们必须知道用于获得正确结果的正确编解码器。例如,如果我们尝试使用UTF-8解码上面的UTF-16编码版本的nonlat:
1 2 3 4 5 | # We can use the method directly on the bytes >>> b'\xff\xfeW['.decode('utf-8') Traceback (most recent call last): File "<stdin>", line 1, in <module> UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte |
我们收到错误!现在,如果我们使用正确的编解码器,结果很好:
1 2 | >>> b'\xff\xfeW['.decode('utf-16') '字' |
在这种情况下,我们被Python警告,因为解码操作失败,但需要注意的是,当编解码器不正确时,错误不会总是发生!这是因为编解码器通常使用相同的代码短语(组成字节对象的“\ xXXX”转义)但代表不同的东西!如果我们在人类语言的背景下考虑这一点,使用不同的编解码器来编码和解码相同的信息就像试图将一个或多个单词从西班牙语翻译成英语并使用意大利语 - 英语词典 - 意大利语中的一些音素和西班牙语可能类似,但你仍然会留下错误的翻译!
在Python 3.x中将非ASCII数据写入文件
作为Python 3.x和Python 2.x中字符串的最后一点,我们必须要记住,使用该open
方法写入两个分支中的文件将不允许Unicode字符串(包含非ASCII字符)写入文件。为此,必须对字符串进行编码。
这在Python 2.x中没什么大不了的,因为如果你这样做(通过使用unicode
方法或者str.decode
),字符串将只是Unicode ,但是在Python 3.x中,所有字符串默认都是Unicode,所以如果我们想写的话这样的字符串,例如nonlat
,文件,我们需要使用str.encode
和wb
(二进制)模式open
将字符串写入文件而不会导致错误,如下所示:
1 2 | >>> with open('nonlat.txt', 'wb') as f: f.write(nonlat.encode()) |