字符串
很久以前,刚接触IT知识的时候,我觉得字符串还有字符变量是很奇葩的存在。数字,数组,字典什么的这些数据类型要不就是自然界固有的要不就是为了计算方便而发明出来的一些数据的组合方式。但是字符串这玩意儿的存在很突兀,它既不是自然界固有的(在人类发明创造的语言中才有意义)也不是一种很NB的数据结构。后来才慢慢意识到,字符串为什么重要,因为人始终还是要看字符串的呀!字符串这个数据类型,可以说是一个机器语言和人类语言之间的一个interpreter。有它在人类才能看懂机器的输出不是。
恩。。扯远了,这篇里不讲逻辑上字符串该怎么用(http://www.cnblogs.com/franknihao/p/6616635.html),也不讲编码什么的(http://www.cnblogs.com/franknihao/p/6557559.html)而是主要聚焦在python中字符串的书写方法,以及一些乱七八糟的知识上。可以算是一篇字符串专题的零碎把= =因为字符串这玩意儿用得太频繁了,经常会遇到问题。。
■ 关于单引号和双引号的区别
python中用双引号和单引号都可以表示字符串,但是它们并不是什么时候都可以乱用的。
单纯地表示一个字符串比如helloworld的话,确实s = "helloworld"和s = 'helloworld'是一样的。但是当字符串本身带有引号(单引号或双引号)的时候情况就不一样了。比如我要print出来的结果是I say "hello"的话,在语句里如果用双引号写那么内容中的双引号就要转义了: s = "I say \"hello\"",但是如果用单引号写就不用: s = 'I say "hello"'。到底是用单引号还是双引号主要还是看个人习惯。同样的,如果是I say 'hello'也是一样的,这样的话就是语句中用单引号要转义,双引号不用。那么可不可以在双引号串里面写单引号转义呢?答案也是可以的,python会对标识要转义的部分进行转义操作。也就是说s = "'"和s = "\'"print出来之后都是一个单引号。
这是针对单引号和双引号的转义,对于其他的转义字符,比如\\来表示一个反斜杠,是一样的
■ 关于字符串前缀
众所周知,python的字符串前可以加上r,u之类的前缀。
前面加上u的字符串,意思是这个字符串应不进行其他编码而直接以unicode码存到内存中。对于字符串,尤其是非纯ascii字符串,默认情况下python都是按照前面几行的# coding=xxx指定的编码来存储字符串的。如果是前缀了u的话,那么就不管这个编码是什么,都按照unicode的编码来存储字符串。以unicode存储的好处在于在不同环境中可以根据不同的编码来进行不同的encode。
前缀r的意思是raw。从一个实例来看:
>>>s = "\t" >>>k = r"\t" >>>s '\t' >>>print s >>>k '\\t' >>>print k \t
一般字符串在储存时不会做任何处理,写出来的是什么存进内存的就是什么,这就导致在print的时候如果字符串中含有可转义的字符的话就会被转义掉。但raw的字符串在储存时会对字符串的可转义字符进行反转义,所以引号里写什么print出来的就是什么。(这个和repr不一样,repr真的是写什么就print什么,连引号都会print出来)。从功能上来说,加不加r其实是解决字符串歧义的问题的,因为python不知道你写下这串字符串,是真的想要这串字符串呢还是想让他有个转义的呢?所以增加了r这个功能。另外再插两句话,
1. 当反斜杠后面跟着的字符和反斜杠不形成转义字符的话,那么python其实就可以断定你是想在print出来的时候结果中有反斜杠的,所以在存进内存的时候python会对这个反斜杠自动做反转义,不管你是否加了r:
>>>s = "a\.b" >>>s 'a\\.b' >>>k = r"a\.b" >>>k 'a\\.b' #两者的结果是一样的,前者是python自动为用户反转义的结果 >>> s = "a\\.b" >>>s 'a\\.b' #这种是比较标准的写法,因为如果希望在print结果中有一个反斜杠,确实应该在原文中写两个反斜杠 >>>k = r"a\\.b" >>>k 'a\\\\.b' #由于前面加了r,所以存进内存时被反转义成四个反斜杠的样子,这样print出来才能有两个反斜杠
2. python无法处理r"\",因为解释器会把这个解释成一个内含一个转义双引号的字符串,所以会报SyntaxError说明引号没有成对出现。所以在python中,如果期望print出来是一个反斜杠,那么就一定要写"\\",不能有r前缀的。(顺便r"\\"在内存中就是四条反斜杠了)
■ unicode-escape,string-escape
今天碰到了个没见过的玩意儿。一个字符串s居然用了s.decode('unicode-escape')这样的操作。unicode-escape显然和utf-8,gbk这些编码格式不一样。
搜了下,发现这个编码的作用主要是这样的:
如果有s = u'\u4f60\u597d',我们知道s是一个unicode类型。实际上在u'xxx'的xxx中,\u是会被解释成声明unicode编码,因此print s的时候会显示编码处理过后的内容。这里具体是“你好”两个汉字。
但是如果一不小心,字符串前面的类型声明u忘记写了,变成了s = '\u4f60\u597d'(或者'\\u4f60\\u597d',\u在一般字符串中没有转义解释,所以加不加两个斜杠都一样)的话。此时\u会被按照一般的字符解释,所以print出来的就是\u4f60\u597d。一般我们会想到是不是可以unicode(s)把它强行转换成unicode类型,但是实验证明此时解释器仍然把s作为一个完整的字符串解释。
此时就要用到unicode-escape了,即s.decode('unicode-escape'),之后,s就又变回了unicode类型的u'\u4f60\u597d'。同理,如果就是想把一个unicode给整成\uxxx形式的字符串,那么就encode('unicode-escape')就行了。
那么这个功能可能在哪里用到呢。比如非常常用的json.dumps方法,dumps方法会默认用默认的编码格式utf-8将所有字符串都decode成unicode(当然如果字符串是gbk之类的编码就会报错decodeerror)。比如:
# -*- coding:utf-8 -*- import json print repr(json.dumps({'a': '你好'})) # 输出是'{"a": "\\u4f60\\u597d"}'
但是如果固定传输时字符串的编码要是指定的比如utf-8,此时我们传送的这个就不符合要求了。那么就可以 json.dumps({'a': '你好'}).decode('unicode-escape').encode('utf-8'),此时再看下,输出中中文字符的部分就变成了\x86\xa3之类的了。
● 消化之后再详述一次
Python中(尤其是Python2)的字符串简直就是一个黑洞,因为字符串这个东西有很多中表达的形式。比如在Python Shell里面,我们打
1. a = u'你好'
2. a = u'\u4f60\u597d'
3. a = '你好'
4. a = '\xc4\xe3\xba\xc3' (要求Shell的编码环境是gb系列,比如windows的cmd)
5. a = '\xe4\xbd\xa0\xe5\xa5\xbd' (要求Shell的编码环境是utf-8,比如linux的bash)
得到的a都可以用来表达【你好】这样两个汉字,print a语句执行后,stdout中也都会出现【你好】两个字。
下面我们来捋一下这几个形式之间的关系。
1、2在Python2中的属于unicode类,后三者则都是str类。也就是说,根据类属分,上面五个形式基本上可以分成1、2 以及3、4、5两个大类。两个大类之间的转换方式,我们十分熟悉,就是encode,decode再配合上指定的编码格式如utf-8或者gbk等。不多说
先来关注str类型。我们应该注意到,在我们使用a = '\xc4\xe3\xba\xc3'这个表达式进行赋值运算的时候(下面例子以gbk编码环境为例),'\xc4\xe3\xba\xc3'本身是一个字符串,它的[0]是"\",[1]是x,[2]是c…这样子。然而神奇的是,当赋值到a之后,a[0]变成了\xc4\xe3。这个和一般的字符串赋值不同,说明其中Python做了处理。稍微玩过Python2的知道,如果想要获得字符串本身作为a的值,那么只要令 a = r'\xc4…'就行了。这个是题外话。
现在我们要解决的,就是找出一种方法,使我们可以手动地模拟上面说到的这种处理。这样编程时就可以更加自由一些。如果要求不是很严格,有一种解决方案,就是使用repr函数。repr('你好')会得到"'\\xc4\\xe3\\xba\\xc3'",这个结果两端会带个引号表明它是一个字符串类型的,可以采用[1:-1]的办法来把引号去掉,但总觉得不是很优雅。优雅的方法,便是使用escape编码了。
首先我们小定义一个东西,正如unicode可以encode成str,str可以decode成unicode,因此可以认为unicode比str是更加“底层”的存在;我们认为【你好】这样一段内存中的内容比r"\xc4\xe3\xba\xc3"这样一个字符串更加底层。(其实这个事情挺微妙,反正我觉得倒过来也行…)。这样就解决了encode和decode分别是谁来调用的问题。
好了,有了以上所述的所有知识,就很容易的可以掌握'string-escape'这种“编码格式”。如果令s = '你好',我们可以预见s.encode('string-escape')得到的内容就是字符串r'\xc4\xe3\xba\xc3',也就是repr(s)[1:-1]。反过来,如果我们通过某种手段得到了r'\xc4\xe3\xba\xc3',将其decode('string-escape')就可以得到【你好】这个内容的字符串了。顺便一提,将上面提到的s继续decode('string-escape')不报错但s也不变,就好比'abc'.encode('ascii')还是'abc'一样,没有变化的。
string-escape就算是讲完了,相对的unicode-escape几乎一样,只是能够用unicode-escape来encode和decode的,只有unicode类实例。也就是说,unicode-escape这种“编码格式”是用来进行上述1,2两种形式之间转换的。repr(u)[2:-1] == u.encode('unicode-escape')(前下标是2的原因是因为repr出来开头是u'xxx,有u和单引号两个字符了)。