print('C:\nowhere')
C: owhere
产生这个问题的原因很复杂。在这里只需要记住,计算机的表达能力是有限的。为了更好地表达,就不得不引入一些额外的表达方式。
为了尽可能自然是表达上面的路径,Python引入了原始字符串的表达方式。
原始字符串以 r
开头,可以在原始字符串中放入任何字符,但不能以反斜线“ \
”结尾,用来防止反斜线转义。例如:
print(r'C:\nowhere')
C:\nowhere
看起来可在原始字符串中包含任何字符,这大致是正确的。 一个例外是,引号需要像通常那样进行转义,但这意味着用于执行转义的反斜杠也将包含在最终的字符串中。
print(r'Let\'s go!')
Let\'s go!
注意: 如果要指定以反斜杠结尾的原始字符串(如以反斜杠结尾的DOS路径),以下方式不对。
# print(r'C:\Program Files\foo\bar\') #此代码会报错
print(r'C:\Program Files\foo\bar\\')
C:\Program Files\foo\bar\\
该如何办呢?下面是一个简单的示例:
print(r'C:\Program Files\foo\bar' '\\')
C:\Program Files\foo\bar\
请注意,指定原始字符串时,可使用单引号或双引号将其括起,还可使用三引号将其括起。
Python字符串使用Unicode编码来表示文本。大致而言,每个Unicode字符都用一个码点(code point)表示,
而码点是Unicode标准给每个字符指定的数字。这能够以任何现代软件都能识别的方式表示129个文字系统中的12万个以上的字符。
当然,鉴于计算机键盘不可能包含几十万个键,因此有一种指定Unicode字符的通用机制:
使用16或32位的十六进制字面量(分别加上前缀 \u
或 \U
)或者使用字符的Unicode名称( \N{name}
)。
"\u00C6"
'Æ'
"\U0001F60A"
'😊'
"This is a cat: \N{Cat}"
'This is a cat: 🐈'
Unicode的理念很简单,却带来了一些挑战,其中之一是编码问题。在内存和磁盘中, 所有对象都是以二进制数字(0和1)表示的(这些数字每8个为一组,即1字节), 字符串也不例外。在诸如C等编程语言中,这些字节完全暴露,而字符串不过是字节序列而已。 为与C语言互操作 以及将文本写入文件或通过网络套接字发送出去, Python提供了两种类似的类型:不可变的bytes 和可变的bytearray。如果需要, 可直接创建bytes对象(而不是字符串),方法是使用前缀b:
b'Hello, world!'
b'Hello, world!'
然而,1字节只能表示256个不同的值,离Unicode标准的要求差很远。
Python bytes字面量只 支持ASCII标准中的128个字符,而余下的128个值必须用转义序列表示,
如 \xf0
表示十六进制值 0xf0
(即 240
)。
唯一的差别好像在于可用的字母表规模,但实际上并非完全如此。 乍一看,好像ASCII和 Unicode定义的都是非负整数和字符之间的映射, 但存在细微的差别:Unicode码点是使用整数定 义的,而ASCII字符是使用对应的数及其二进制编码定义的。 这一点好像无关紧要,原因之一是 整数0~255和8位二进制数之间的映射是固定的,几乎没有任何机动空间。 问题是超过1字节后, 情况就不那么简单了:直接将每个码点表示为相应的二进制数可能不再可行。 这是因为不仅存在 字节顺序的问题(即便对整数值进行编码,也会遇到这样的问题), 而且还可能浪费空间:如果 对于每个码点都使用相同数量的字节进行编码, 就必须考虑到文本可能包含安那托利亚象形文字 或皇家亚兰字母。 有一种Unicode编码标准是基于这种考虑的,它就是UTF-32(32位统一编码转换格式,Unicode Transformation Format 32 bits), 但如果主要处理的是使用互联网上常见语言书写的文本,那么使用这种编码标准将很浪费空间。
然而,有一种非常巧妙的替代方式:不使用全部32位,而是使用变长编码, 即对于不同的字 符,使用不同数量的字节进行编码。这种编码方式主要出自计算机先锋Kenneth Thompson之手。 通过使用这种编码,可节省占用的空间,就像摩尔斯码使用较少的点和短线表示常见的字母, 从而减少工作量一样。具体地说,进行单字节编码时,依然使用ASCII编码,以便与较旧的系统兼容; 但对于不在这个范围内的字符,使用多个字节(最多为6个)进行编码。 下面来使用ASCII、UTF-8和UTF-32编码将字符串转换为bytes。
"Hello, world!".encode("ASCII")
b'Hello, world!'
"Hello, world!".encode("UTF-8")
b'Hello, world!'
"Hello, world!".encode("UTF-32")
b'\xff\xfe\x00\x00H\x00\x00\x00e\x00\x00\x00l\x00\x00\x00l\x00\x00\x00o\x00\x00\x00,\x00\x00\x00 \x00\x00\x00w\x00\x00\x00o\x00\x00\x00r\x00\x00\x00l\x00\x00\x00d\x00\x00\x00!\x00\x00\x00'
从中可知,使用前两种编码的结果相同,但使用最后一种编码的结果长得多。再来看一个示例:
len("How long is this?".encode("UTF-8"))
17
len("How long is this?".encode("UTF-32"))
72
只要字符串包含较怪异的字符,ASCII和UTF-8之间的差别便显现出来了:
# "Hællå, wørld!".encode("ASCII") #此代码会报错
斯堪的纳维亚字母没有对应的ASCII编码。
如果必须使用ASCII编码(这样的情况肯定会遇到),可向 encode
提供另一个实参,告诉它如何处理错误。
这个参数默认为 strict
,但可将其指定为其他值,以忽略或替换不在 ASCII 表中的字符。
"Hællå, wørld!".encode("ASCII", "ignore")
b'Hll, wrld!'
"Hællå, wørld!".encode("ASCII", "replace")
b'H?ll?, w?rld!'
"Hællå, wørld!".encode("ASCII", "backslashreplace")
b'H\\xe6ll\\xe5, w\\xf8rld!'
"Hællå, wørld!".encode("ASCII", "xmlcharrefreplace")
b'Hællå, wørld!'
几乎在所有情况下,都最好使用UTF-8。事实上,它也是 Python3 中默认使用的编码。
"Hællå, wørld!".encode()
b'H\xc3\xa6ll\xc3\xa5, w\xc3\xb8rld!'
这相比于Hello, world!
,编码结果要长些;但使用UTF-32编码时,结果一样长。
可将字符串编码为bytes
,同样也可将bytes
解码为字符串。
b'H\xc3\xa6ll\xc3\xa5, w\xc3\xb8rld!'.decode()
'Hællå, wørld!'
与前面一样,默认编码也是UTF-8。可指定其他编码,但如果指定的编码不正确,
将出现错误消息或得到一堆乱码。bytes
对象本身并不知道使用的是哪种编码,
因此必须负责跟踪这一点。
可不使用方法encode
和decode
,而直接创建bytes
和str
(即字符串)对象,如下所示:
bytes("Hællå, wørld!", encoding="utf-8")
b'H\xc3\xa6ll\xc3\xa5, w\xc3\xb8rld!'
str(b'H\xc3\xa6ll\xc3\xa5, w\xc3\xb8rld!', encoding="utf-8")
'Hællå, wørld!'
这种方法更通用一些,在不知道类似于字符串或bytes
的对象属于哪个类时,使用这种方法也更管用。
一个通用规则是,不要做过于严格的假设。
编码和解码的最重要用途之一是,将文本存储到磁盘文件中。然而, Python提供的文件读写机制通常会代替完成这方面的工作!只要文件使用的是UTF-8编码, 就无需操心编码和解码的问题。但如果原本正常的文本变成了乱码, 就说明文件使用的可能是其他编码。在这种情况下,对导致这种问题的原因有所了解将大有裨益。
最后,Python还提供了bytearray
,它是bytes
的可变版。
从某种意义上说,它就像是可修改的字符串,常规字符串是不能修改的。
然而,bytearray
其实是为在幕后使用而设计的,因此作为类字符串使用时对用户并不友好。
例如,要替换其中的字符,必须将其指定为0~255的值。
因此,要插入字符,必须使用ord
获取其序数值(ordinal value)。
x = bytearray(b"Hello!")
x[1] = ord(b"u")
x
bytearray(b'Hullo!')