您的位置:首页 > 编程语言 > Python开发

python浓缩(6)序列: 字符串、列表和元 组

2016-04-11 00:00 537 查看
它们的成员有序排列的,并且可以通过下标偏移量访问到它的一个或者几个成员,称为序列,包括下面这些:字符串(普通字符串和unicode 字符串),列表,和元组类型。

首先我们来熟悉一下适用于所有序列类型的操作符和内建函数(BIFs),

􀁺 简介

􀁺 操作符

􀁺 内建函数

􀁺 内建函数(如果可用)

􀁺 特性(如果可用)

􀁺 相关模块(如果可用)



6.1 序列

序列类型有着相同的访问模式:它的每一个元素可以通过指定一个偏移量的方式得到。而多个元素可以通过切片操作的方式一次得到。

下标偏移量是从0 开始到 总元素数-1 结束 -- 之所以要减一是因为我们是从0 开始计数的。

6.1.1 标准类型操作符

标准类型操作符(参见4.5 节)一般都能适用于所有的序列类型。当然,如果作复合类型的对象比较的话,这样说可能需要有所保留,不过其他的操作绝对是完全适用的。

6.1.2 序列类型操作符

表6.1 列出了对所有序列类型都适用的操作符。操作符是按照优先级从高到底的顺序排列的。



连接操作符( + )

这个操作符允许我们把一个序列和另一个相同类型的序列做连接。语法如下:

sequence1 + sequence2

注意,这个操作不是最快或者说最有效的。对字符串来说,这个操作不如把所有的子字符串放到一个列表或可迭代对象中,然后调用一个join方法来把所有的内容连接在一起节约内存;类似地,对列表来说,我们推荐读者用列表类型的extend()方法来把两个或者多个列表对象合并.当你需要简单地把两个对象的内容合并,或者说不能依赖于可变对象的那些没有返回值(实际上它返回一个None)的内建方法来完成的时候时,连接操作符还是很方便的一个选择。

重复操作符 ( * )

当你需要需要一个序列的多份拷贝时,重复操作符非常有用,它的语法如下:

sequence * copies_int

copies_int 必须是一个整数(1.6 节里面有讲到,不能是长整数).像连接操作符一样,该操作符返回一个新的包含多份原对象拷贝的对象。

切片操作符 ( [], [:], [::] )

序列允许通过指定下标的方式来获得某一个数据元素,或者通过指定下标范围来获得一组序列的元素.这种访问序列的方式叫做切片。访问某一个数据元素的语法如下:

sequence[index]


偏移量可以是正值,范围从0 到偏移最大值(比序列长度少一),用len()函数可以得到序列长度,实际范围: 0 <= inde <= len(sequece)-1 ;

使用负索引,范围是 -1 到序列的负长度,-len(sequence), -len(sequence) <= index <= -1;

正负索引的区别在于正索引以序列的开始为起点,负索引以序列的结束为起点.试图访问一个越界的索引会引发一个如下的异常:

>>> names = ('Faye', 'Leanna', 'Daylen')
>>> print names[4]
Traceback (most recent call last): File "<stdin>", line 1, in ?
IndexError: tuple index out of range

因为Python 是面向对象的,所以你可以像下面这样直接访问一个序列的元素(不用先把它赋值给一个变量):

>>> print ('Faye', 'Leanna', 'Daylen')[1]
Leanna

这个特性在你调用一个返回值是序列类型的函数,并且你只对返回的序列中的一个或某几个元素感兴趣时特别有用.

如何一次得到多个元素呢?

sequence[starting_index:ending_index]

起始索引和结束索引都是可选的,如果没有提供或者用None 作为索引值,切片操作会从序列的最开始处开始,或者直到序列的最末尾结束.

用步长索引来进行扩展的切片操作

多出来的第三个索引值被用做步长参数。 Python 的虚拟机里面其实很早就有了扩展切片操作,eg:

>>> s = 'abcdefgh'
>>> s[::-1] # 可以视作"翻转"操作
'hgfedcba'
>>> s[::2] # 隔一个取一个的操作
'aceg'

切片索引的更多内容

切片索引的语法要比简单的单一元素索引灵活的多。开始和结束素引值可以超过字符串的长度。简单地说,即使用100 来作为一个长度不到100 的序列的结束索引也不会有什么错误,例子如下:

>>> ('Faye', 'Leanna', 'Daylen')[-100:100]
('Faye', 'Leanna', 'Daylen')

有一个字符串,我们想通过一个循环按照这样的形式显示它:每次都把位于最后的一个字符砍掉,下面是实现这个要求的一种方法:

>>> s = 'abcde'
>>> i = -1
>>> for i in range(-1, -len(s), -1):
... print s[:i]
... abcd
abc
ab
a

可是,该如何在第一次迭代的时候显示整个字符串呢?是否有一种方法可以不用在整个循环之前加入一个额外的print 语句呢?以负数作为索引的例子里是不能解决这个的方法,因为-1 已经是“最小”的索引了。

可以使用另一个小技巧:用None 作为索引值,这样一来就可以满足你的需要,比如说,在你想用一个变量作为索引来从第一个到遍历最后一个元素的时候:

>>> s = 'abcde'
>>> for i in [None] + range(-1, -len(s), -1):
... print s[:i]
...
abcde
abcd
abc
ab
a

现在这个程序符合我们的要求了。似乎还可以先创建一个只包含None 的列表,然后用extend()函数把range()的输出添加到这个列表,或者先建立range()输出组成的列表然后再把None 插入到这个列表的最前面,然后对这个列表进行遍历,但是可变对象的内建函数extend()根本就没有返回值,所以这个方法是行不通的:

>>> for i in [None].extend(range(-1, -len(s), -1)):
... print s[:i]
...
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: iteration over non-sequence

这个错误发生的原因是[None].extend(...)函数返回None , None 既不是序列类型也不是可迭代对象.

6.1.3 内建函数(BIFs)

序列本身就内含了迭代的概念,因为迭代这个概念就是从序列,迭代器,或者其他支持迭代操作的对象中泛化得来的。由于Python 的for 循环可以遍历所有的可迭代类型,在(非纯序列对象上)执行for 循环时就像在一个纯序列对象上执行一样。而且Python 的很多原来只支持序列作为参数的内建函数现在也开始支持迭代器或者或类迭代器了.我们把这些类型统称为"可迭代对象".在这一章里我们会详细的讨论跟序列关系紧密的内建函数(BIF).

类型转换

内建函数list(),str()和tuple()被用做在各种序列类型之间转换。你可以把它们理解成其他语言里面的类型转换,但是并没有进行任何的转换。这些转换实际上是工厂函数(在第4章介绍),将对象作为参数,并将其内容(浅)拷贝到新生成的对象中.表6.2 列出了适用于序列类型的转换函数。



我们又用了一次“转换”这个词。不过,为什么Python 里面不简单地把一个对象转换成另一个对象呢?回过头看一下第4 章就会知道,一旦一个Python 的对象被建立,我们就不能更改其身份或类型了.如果你把一个列表对象传给list()函数,便会创建这个对象的一个浅拷贝,然后将其插入新的列表中。同样地,在做连接操作和重复操作时,我们也会这样处理。所谓浅拷贝就是只拷贝了对对象的索引,而不是重新建立了一个对象!如果你想完全的拷贝一个对象(包括递归,如果你的对象是一个包含在容器中的容器),你需要用到深拷贝,关于浅拷贝和深拷贝的更多信息会在本章的末尾讲到。

str()函数在需要把一个对象的可打印信息输出时特别有用,不仅仅是对序列类型,对其他类型的对象同样如此.

Unicode()是str()函数的unicode 版本,它跟str()函数基本一样.

list()和tuple()函数在列表类型和元组类型的互换时非常有用.不过,虽然这些函数也适用于string类型(因为string 类型也是序列的一种),但是在string 类型上应用tuple()和list()函数却得不到我们通常希望的结果.

Operational





我们将分别在每个序列的章节里面提供使用这些函数的例子.

6.2 字符串

Python 里面单引号和双引号的作用是相同的。Python 用"原始字符串"操作符来创建直接量字符串,所以再做区分就没什么意义了.

几乎所有的Python 应用程序都会某种方式用到字符串类型.字符串是一种直接量或者说是一种标量,这意味着Python 解释器在处理字符串时是把它作为单一值并且不会包含其他Python 类型的。字符串是不可变类型,就是说改变一个字符串的元素需要新建一个新的字符串.

根据在2.2 章节里面对类型和类的概念进行的统一,Python 实际上有3 类字符串.通常意义的字符串(str)和Unicode 字符串(unicode)实际上都是抽象类basestring 的子类.这个basestring 是不能实例化的,如果你试图实例化一个basestring 类,你会得到以下报错信息:

>>> basestring('foo')
Traceback (most recent call last): File "<stdin>", line 1, in <module>
TypeError: The basestring type cannot be instantiated

字符串的创建和赋值

创建一个字符串就像使用一个标量一样简单,当然你也可以把str()作为工厂方法来创建一个字符串并把它赋值给一个变量:

>>> aString = 'Hello World!' # 使用单引号
>>> anotherString = "Python is cool!" # 使用双引号
>>> print aString # print 不带引号的 Hello World!
>>> anotherString # 不是进行print 操作,带有引号
'Python is cool!'
>>> s = str(range(4)) # 把一个列表转换成一个字符串
>>> s
'[0, 1, 2, 3]'

如何访问字符串的值(字符和子串)

Python 里面没有字符这个类型,而是用长度为1 的字符串来表示这个概念,当然,这其实也是一个子串。

如何改变字符串

你可以通过给一个变量赋值(或者重赋值)的方式“更新”一个已有的字符串。

>>> aString = aString[:6] + 'Python!'
>>> aString
'Hello Python!'
>>> aString = 'different string altogether'
>>> aString
'different string altogether'

跟数字类型一样,字符串类型也是不可变的,所以你要改变一个字符串就必须通过创建一个新串的方式来实现。也就是说你不能只改变一个字符串的一个字符或者一个子串,然而,通过拼凑一个旧串的各个部分来得到一个新串是被允许的,正如上面你看到的那样.

如何删除字符和字符串

再重复一遍,字符串是不可变的,所以你不能仅仅删除一个字符串里的某个字符,你能做的是清空一个空字符串,或者是把剔除了不需要的部分后的字符串组合起来形成一个新串。假设你想要从"Hello World!"里面删除小写的'l'

>>> aString = 'Hello World!'
>>> aString = aString[:3] + aString[4:]
>>> aString
'Helo World!'

通过赋一个空字符串或者使用del 语句来清空或者删除一个字符串:

>>> aString = ''
>>> aString
''
>>> del aString

在大部分应用程序里,没有必要显式的删除字符串。定义这个字符串的代码最终会结束,那时Python 会自动释放这些字符串.

6.3 字符串和操作符

6.3.1 标准类型操作符

在第4 章里面,我们介绍了一些适用于包括标准类型在内的大部分对象的操作符,在这里再看一下这些其中的一些操作符是怎样作用于字符串类型的,下面是几个简单的例子:

>>> str1 = 'abc'
>>> str2 = 'lmn'
>>> str3 = 'xyz'
>>> str1 < str2
True
>>> str2 != str3
True
>>> str1 < str3 and str2 == 'xyz'
False

在做比较操作的时候,字符串是按照ASCII 值的大小来比较的.

6.3.2 序列操作符

切片( [ ] 和 [ : ] )

正向索引

反向索引

默认索引

接下来以字符串'abcd'为例子.可以用长度操作符来确认该字符串的长度是4:

>>> aString = 'abcd'
>>> len(aString)
4

用一个参数来调用切片操作符结果是一个单一字符,而使用一个数值范围(用':')作为参数调用切片操作的参数会返回一串连续地字符.再强调一遍,对任何范围[start:end],我们可以访问到包括start 在内到end(不包括end)的所有字符,

>>> aString[0]
'a'
>>> aString[1:3]
'bc'
>>> aString[2:4]
'cd'
>>> aString[4]
Traceback (innermost last): File "<stdin>", line 1, in ?
IndexError: string index out of range

使用不在允许范围(本例中是0 到3)内的索引值会导致错误。上面的aString[2:4]却并没有出错,那是因为实际上它返回的是索引值2 和3 的值。但是直接拿4 作为索引访问是不被允许的。

在进行反向索引操作时,是从-1 开始,向字符串的开始方向计数,到字符串长度的负数为索引的结束:

>>> aString[-1]
'd'
>>> aString[-3:-1]
'bc'
>>> aString[-4]
'a'

如果开始索引或者结束索引没有被指定,则分别以字符串的第一个和最后一个索引值为默认值。

>>> aString[2:]
'cd'
>>> aString[1:]
'bcd'
>>> aString[:-1]
'abc'
>>> aString[:]
'abcd'

注意:起始/结束索引都没有指定的话会返回整个字符串.

【其实感觉没有指定的话,应该是最后一个索引值+1,因为如果以最后一个索引值为默认的话,应该不会显示d,按照切片的定义来说】

成员操作符(in ,not in)

成员操作符用于判断一个字符或者一个子串(中的字符)是否出现在另一个字符串中。注意,成员操作符不是用来判断‍一个字符串是否包含另一个字符串的【谁能告诉我这个啥意思,示例里面不就是判断一个字符串包含一个字符串了吗】‍,这样的功能由find()或者index()(还有它们的兄弟:rfind()和rindex())函数来完成)

下面是一些字符串和成员操作符的例子.

>>> 'bc' in 'abcd'
True
>>> 'n' in 'abcd'
False
>>> 'nm' not in 'abcd'
True

在例6.1 里面,我们会用到下面这些string 模块预定义的字符串:

>>> import string
>>> string.ascii_uppercase
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
>>> string.ascii_lowercase
'abcdefghijklmnopqrstuvwxyz'
>>> string.ascii_letters
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
>>> string.digits
'0123456789'

例 6.1 标识符检查 (idcheck.py)标识符合法性检查。这个小例子只检查长度大于等于2 的标识符.

# 导入string 模块并且预定义了两个字符串
import string
alphas = string.ascii_letters + '_'
nums = string.digits

inp = input('Identifier to test? ')
if len(inp) > 1:
if inp[0] not in alphas:
print ("invalid first symbol")
else:
for otherChar in inp[1:]:
if otherChar not in alphas +nums:
print ("invalid other char")
break
print ("ok")
else:
print ("len > 2")

这个例子还展示了字符串连接符( + )的使用。

核心提示: 性能

从性能的的角度来说,把重复操作作为参数放到循环里面进行是非常低效的.

while i < len(myString):
print 'character %d is:', myString[i]

上面的循环操作把大把的时间都浪费到了重复计算字符串myString 的长度上了.每次循环迭代都要运行一次这个函数.如果把这个值做一次保存,我们就可以用更为高效的方式重写我们的循环操作.

length = len(myString)
while i < length:
print 'character %d is:', myString[i]

这个方法同样适用于上面的例6.1

for otherChar in myInput[1:]:
if otherChar not in alphas + nums:

在这个if 里面执行了合并两个字符串的操作。被合并的这两个字符串从始至终就没变过,而每次都会重新进行一次计算.如果先把这两个字符串存为一个新字符串,我们就可以直接引用这个字符串而不用进行重复计算了。

alphnums = alphas + nums
for otherChar in myInput[1:]:
if otherChar not in alphnums:

这段程序并没有想到Python 的关键字,而这些都是作为保留字,不允许用做标识符的.我们把这作为课后作业留给读者(见练习6-2)。

连接符( + )

运行时刻字符串连接,我们可以通过连接操作符来从原有字符串获得一个新的字符串.eg:

>>> 'Spanish' + 'Inquisition'
'SpanishInquisition'
>>> 'Spanish' + ' ' + 'Inquisition'
'Spanish Inquisition'
>>> s = 'Spanish' + ' ' + 'Inquisition' + ' Made Easy'
>>> s
'Spanish Inquisition Made Easy'
>>> import string
>>> string.upper(s[:3] + s[20]) # archaic (see below)
'SPAM'

最后一个例子展示了用一个字符串s 的两个切片来构成一个新串的操作,,该方法负责把字符串的所有字符都变为大写。

String 模块的方法是在Python1.6 里面添加进来的,所以这个操作也可以用最后一个字符串的一个单一方法调用来完成。现在已经没有必要导入string 模块了,除非你需要访问该模块自己定义的字符串常量。出于性能方面方面,建议不要用string 模块。原因是Python 必须为每一个参加连接操作的字符串分配新的内存,包括新产生的字符串。推荐使用字符串格式化操作符(%),或者把所有的字符串放到一个列表中去,然后用一个join()方法来把它们连接在一起。

>>> '%s %s' % ('Spanish', 'Inquisition')
'Spanish Inquisition'
>>>
>>> s = ' '.join(('Spanish', 'Inquisition', 'Made Easy'))
>>> s
'Spanish Inquisition Made Easy'
>>>
>>> # no need to import string to use string.upper():
>>> ('%s%s' % (s[:3], s[20])).upper()
'SPAM'

编译时字符串连接

上面的语法在运行时字符串连接的加法操作,这个用法是非常标准的。Python 中还有一种并不是经常用到,更像是一种程序员的习惯用法的语法.Python 的语法允许你在源码中把几个字符串连在一起写,以此来构建新字符串:

>>> foo = "Hello" 'world!'
>>> foo
'Helloworld!'

通过这种方法,你可以把长的字符串分成几部分来写,而不用加反斜杠。

你可以在一行里面混用两种分号。这种写法的好处是你可以把注释也加进来,如下:

>>> f = urllib.urlopen('http://' # protocol
... 'localhost' # hostname
... ':8000' # port
... '/cgi-bin/friends2.py') # file

如你所想,下面就是urlopen()方法所得到的真实输入:

>>> 'http://' 'localhost' ':8000' '/cgi-bin/friends2.py'
'http://localhost:8000/cgi-bin/friends2.py'

普通字符串转化为Unicode 字符串

如果把一个普通字符串和一个Unicode 字符串做连接处理,Python 会在连接操作前先把普通字符串转化为Unicode 字符串:

>>> 'Hello' + u' ' + 'World' + u'!'
u'Hello World!'

重复操作符( * )

重复操作符创建一个包含了原有字符串的多个拷贝的新串:

>>> 'Ni!' * 3
'Ni!Ni!Ni!'
>>> '*'*40
'****************************************'
>>> print '-' * 20, 'Hello World!', '-' * 20
-------------------- Hello World! --------------------
>>> who = 'knights'
>>> who * 2
'knightsknights'
>>> who
'knights'

像其他的标准操作符一样,原变量是不被修改的,就像上面最后一个例子所示。

6.4 只适用于字符串的操作符

6.4.1 格式化操作符( % )

只适用于字符串类型,并且支持所有printf()式的格式化操作.语法如下:

左边的format_string 里面通常会在printf()函数的第一个参数里面见到的一样:包含%的格式化字符串.arguments_to_convert 参数是你要转化、显示的变量,对应于你送给prinf 的其他参数.

表6.4 列出了可用的各种符号.



Python 支持两种格式的输入参数:元组和字典。字典其实是一个哈希键-值对的集合。这种形式里面,key 是作为格式字符串出现,相对应的value 值作为参数在进行转化时提供给格式字符串.

格式字符串既可以跟print 语句一起用来向终端用户输出数据,又可以用来合并字符串形成新字符串,而且还可以直接显示到GUI(Graphical User Interface)界面上去.

表6.5 格式化操作符辅助指令



以下是一些使用格式字符串的例子:

十六进制输出:
>>> "%x" % 108
'6c'
>>> "%X" % 108
'6C'
>>> "%#X" % 108
'0X6C'
>>> "%#x" % 108
'0x6c'
浮点数和科学记数法形式输出:
>>> '%f' % 1234.567890
'1234.567890'
>>> '%.2f' % 1234.567890
'1234.57'
>>> '%E' % 1234.567890
'1.234568E+03'
>>> '%e' % 1234.567890
'1.234568e+03'
>>> '%g' % 1234.567890
'1234.57'
>>> '%G' % 1234.567890
'1234.57'
>>> "%e" % (1111111111111111111111L)
'1.111111e+21'
整数和字符串输出:
>>> "%+d" % 4
'+4'
>>> "%+d" % -4
'-4'
>>> "we are at %d%%" % 100
'we are at 100%'
>>> 'Your host is: %s' % 'earth'
'Your host is: earth'
>>> 'Host: %s\tPort: %d' % ('mars', 80)
'Host: mars Port: 80'
>>> num = 123
>>> 'dec: %d/oct: %#o/hex: %#X' % (num, num, num)
'dec: 123/oct: 0173/hex: 0X7B'
>>> "MM/DD/YY = %02d/%02d/%d" % (2, 15, 67)
'MM/DD/YY = 02/15/67'
>>> w, p = 'Web', 'page'
>>> 'http://xxx.yyy.zzz/%s/%s.html' % (w, p)
'http://xxx.yyy.zzz/Web/page.html'

上面的例子都是使用的元组类型的参数作转换.下面我们将把字典类型的参数提供给格式化操作符.

>>> 'There are %(howmany)d %(lang)s Quotation Symbols' % \
... {'lang': 'Python', 'howmany': 3}
'There are 3 Python Quotation Symbols'

字符串格式化操作符是一个非常有用的调试工具。事实上,所有的Python 对象都有一个字符串表示形式(通过repr()函数,'' 或str()函数来展现).print 语句自动为每个对象调用str()函数.更好的是,在定义自己的对象时,你可以利用"钩子"为你的对象创建字符串表达形式. 这样,repr(),str()或`` 或者print 被调用时,就可以获得一个适当的字符串描述信息.即使在坏的不能再坏的情况下,repr()或者str()也不能显示一个对象的信息时,Pythonic 方式的默认做法最起码能给你返回想如下格式的信息:

<... something that is useful ...>.

6.4.2 字符串模板: 更简单的替代品

程序员会偶尔出现遗漏转换类型符号的错误,比如说,用了%(lang)而不是正确的%(lang)s.为了保证字符串被正确的转换,程序员必须明确的记住转换类型参数,比如到底是要转成字符串,整数还是其他什么类型.

新式的字符串模板的优势是不用去记住所有的相关细节的,而是像现在shell 风格的脚本语言里面那样使用美元符号($).

由于新式的字符串Template 对象的引进使得string 模块又重新活了过来,Template 对象有两个方法,substitute()和safe_substitute().前者更为严谨,在key 缺少的情况下它会报一个KeyError 的异常出来,而后者在缺少key 时,直接原封不动的把字符串显示出来.

>>> from string import Template
>>> s = Template('There are ${howmany} ${lang} Quotation Symbols')
>>> print s.substitute(lang='Python', howmany=3)
There are 3 Python Quotation Symbols
>>> print s.substitute(lang='Python')
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "/usr/local/lib/python2.4/string.py", line 172, in substitute
return self.pattern.sub(convert, self.template)
File "/usr/local/lib/python2.4/string.py", line 162, in convert val =
mapping[named]
KeyError: 'howmany'
>>> print s.safe_substitute(lang='Python')
There are ${howmany} Python Quotation Symbols

新式的字符串模板是从Python2.4 开始加入的,更多信息请查阅Python 类库手册和PEP292.

6.4.3 原始字符串操作符( r/R )

关于原始字符串的目的,是为了对付那些在字符串中出现的特殊字符(下面的小节会介绍这些特殊字符)。在原始字符串里,所有的字符都是直接按照字面的意思来使用,没有转义特殊或不能打印的字符。

原始字符串的这个特性让一些工作变得非常的方便,比如正则表达式的创建(详见文档的re模块).正则表达式是一些定义了高级搜索匹配方式的字符串,通常是由代表字符,分组、匹配信息、变量名、和字符类等的特殊符号组成。正则表达式模块已经包含了足够用的符号。但当你必须插入额外的符号来使特殊字符表现的像普通字符的时候,你就陷入了“字符数字”的泥潭!这时原始字符串就会派上用场了.除了原始字符串符号(引号前面的字母"r")以外,原始字符串跟普通字符串有着几乎完全相同的语法.

这个'r'可以是小写也可以是大写,唯一的要求是必须紧靠在第一个引号前.

在三个例子的第一个例子里面,我们需要一个反斜杠加一个'n'来而不是一个换行符.:

>>> '\n'
'\n'
>>> print '\n'

>>> r'\n'
'\\n'
>>> print r'\n'
\n

接下来的例子里,我们打不开我们的README 文件了,为什么?因为'\t'和'\r'被当成不在我们的文件名中的特殊符号,但它们实际上文件路径的中4 个独立的字符.

>>> f = open('C:\windows\temp\readme.txt', 'r')
Traceback (most recent call last):
File "<stdin>", line 1, in ?
f = open('C:\windows\temp\readme.txt', 'r')
IOError: [Errno 2] No such file or directory: 'C:\\win- dows\\temp\readme.txt'
>>> f = open(r'C:\windows\temp\readme.txt', 'r')
>>> f.readline()
'Table of Contents (please check timestamps for last update!)\n'
>>> f.close()

最后我们要找一对原始的\n 字符而不是换行。为了找到它,我们使用了一个简单的正则表达式,它的作用是查找通常被用来表示空白字符的反斜线-字符对(backslash-characterpairs)。

>>> import re
>>> m = re.search('\\[rtfvn]', r'Hello World!\n')
>>> if m is not None: m.group()
...
>>> m = re.search(r'\\[rtfvn]', r'Hello World!\n')
>>> if m is not None: m.group()
...
'\\n'


6.4.4 Unicode 字符串操作符( u/U )

Unocide 字符串操作符,大写的(U)和小写的(u)是和 Unicode 字符串一起被引入的. 它用来把标准字符串或者是包含Unicode 字符的字符串转换成完全地Unicode 字符串对象。关于Unicode 字符串的进一步信息在6.7.4 节有详细介绍.另外,字符串方法(见6.6节)和正则表达式引擎也支持Unicode.下面是几个例子:

u'abc' U+0061 U+0062 U+0063
u'\u1234' U+1234
u'abc\u1234\n' U+0061 U+0062 U+0063 U+1234 U+0012

Unicode 操作符也可以接受原始Unicode 字符串, 只要我们将Unicode 操作符和原始字符串操作符连接在一起就可以了. 注意:Unicode 操作符必须出现在原始字符串操作符前面.

ur'Hello\nWorld!'


6.5 内建函数

6.5.1 标准类型函数

cmp() 内建的cmp()函数也根据字符串的ASCII 码值进行比较.

>>> str1 = 'abc'
>>> str2 = 'lmn'
>>> str3 = 'xyz'
>>> cmp(str1, str2)
-11
>>> cmp(str3, str1)
23
>>> cmp(str2, 'lmn')
0


6.5.2 序列类型函数

内建函数len()返回字符串的字符数.

max() and min()
>>> str2 = 'lmn'
>>> str3 = 'xyz'
>>> max(str2)
'n'
>>> min(str3)
'x'

虽然max()和min()函数对其他的序列类型可能更有用,但对于string 类型它们能很好地运行,返回最大或者最小的字符,(按照ASCII 码值排列),下面是几个例子:

>>> min('ab12cd')
'1'
>>> min('AB12CD')
'1'
>>> min('ABabCDcd')
'A'

enumerate()

>>> s = 'foobar'
>>> for i, t in enumerate(s):
... print i, t
...
0 f
1 o
2 o
3 b
4 a
5 r

zip()

>>> s, t = 'foa', 'obr'
>>> zip(s, t)
[('f', 'o'), ('o', 'b'), ('a', 'r')]


6.5.3 字符串类型函数

raw_input()

内建的raw_input()函数使用给定字符串提示用户输入并将这个输入返回,eg:

>>> user_input = raw_input("Enter your name: ")
Enter your name: John Doe
>>> user_input
'John Doe'
>>> len(user_input)
8

Python 里面没有C 风格的结束字符NULL,你输入多少个字符,len()函数的返回值就是多少.

str() and unicode()

str()和unicode()函数都是工厂函数,就是说产生所对应的类型的对象.它们接受一个任意类型的对象,然后创建该对象的可打印的或者Unicode 的字符串表示. 它们和basestring 都可以作为参数传给isinstance()函数来判断一个对象的类型.

>>> isinstance(u'\0xAB', str)
False
>>> not isinstance('foo', unicode)
True
>>> isinstance(u'', basestring)
True
>>> not isinstance('foo', basestring)
False

chr(), unichr(), and ord()

chr()函数用一个范围在range(256)内的(就是0 到255)整数做参数,返回一个对应的字符.

unichr()跟它一样,只不过返回的是Unicode 字符,unichr()的参数范围依赖于你的Python 是如何被编译的.如果是配置为USC2 的Unicode,那么它的允许范围就是range(65536) 或者说0x0000-0xFFFF; 如果配置为UCS4 , 那么这个值应该是range(1114112)或者0x000000-0x110000.如果提供的参数不在允许的范围内,则会报一个ValueError 的异常。

ord()函数是chr()函数(对于8 位的ASCII 字符串)或unichr()函数(对于Unicode 对象)的配对函数,它以一个字符(长度为1 的字符串)作为参数,返回对应的ASCII 数值,或者Unicode数值,如果所给的Unicode 字符超出了你的Python 定义范围,则会引发一个TypeError 的异常。

>>> chr(65)
'A'
>>> ord('a')
97
>>> unichr(12345)
u'\u3039'
>>> chr(12345)
Traceback (most recent call last): File "<stdin>", line 1, in ?
chr(12345)
ValueError: chr() arg not in range(256)
>>> ord(u'\ufffff')
Traceback (most recent call last): File "<stdin>", line 1, in ?
ord(u'\ufffff')
TypeError: ord() expected a character, but string of length 2 found
>>> ord(u'\u2345')
9029


6.6 字符串内建函数

这些方法实现了string 模块中的大部分方法,表6.6 列出了目前字符串内建支持的方法,所有这些方法都包含了对Unicode 的支持,有一些甚至是专门用于Unicode 的.







几个使用字符串方法的例子:

>>> quest = 'what is your favorite color?'
>>> quest.capitalize()
'What is your favorite color?'
>>> quest.center(40)
' what is your favorite color? '
>>> quest.count('or')
2
>>> quest.endswith('blue')
False
>>> quest.endswith('color?')
True
>>> quest.find('or', 30)
-1
>>> quest.find('or', 22)
25
>>> quest.index('or', 10)
16
>>> ':'.join(quest.split())
'what:is:your:favorite:color?'
>>> quest.replace('favorite color', 'quest')
'what is your quest?'
>>> quest.upper()
'WHAT IS YOUR FAVORITE COLOR?'

上面最复杂的例子是有split()和join()函数的那个.首先我们在string 上调用split()函数,没有用参数,也就是说以空格作为分隔符分隔字符串,然后我们以这个包含单词的列表做参数调用join()方法把这些单词用一个新的分隔符冒号重新串在一起,注意,我们首先用split()函数把string 切片成一个列表,然后我们在字符串':'上应用join()方法把这个列表重新连接成一个字符串.

6.7 字符串的独特特性

6.7.1 特殊字符串和控制字符

一个反斜线加一个单一字符可以表示一个特殊字符,通常是一个不可打印的字符,这就是我们上面讨论的特殊字符。

除了通常用的特殊字符,比如换行符(\n),tab 符(\t)之外,也可以直接用ASCII 码值来标示特殊字符:\000 或者\xXX,分别对应字符的八进制和十六进制ASCII 码值,下面分别是十进制,八进制和十六进制的0,65,和255:





特殊字符,包括反斜杠转义的那些都可以像普通字符一样存储到Python 的字符串中.跟C 字符串的另一个不同之处是Python 的字符串并不是以NUL(\000)作为结束符的.NUL 跟其他的反斜杠转义字符没什么两样.事实上,一个字符串中不仅可以出现NUL 字符,而且还可以出现不止一次,在字符串的任意位置都可以。表6.7 列出了被大部分Python 版本支持的转义字符.

如上所述,就像使用连字符来让一行的内容持续到下一行一样,可以用显式定义八进制或者十六进制的ASCII 码的方式定义特殊字符,合法的ASCII 码值范围是从0 到255(八进制的是0177,十六进制是0XFF).

Table 6.7 反斜杠开头的转义字符



控制字符的一个作用是用做字符串里面的定界符,在数据库或者web 应用中,大多数的可打印字符都是被允许用在数据项里面的,就是说可打印的字符不适合做定界符.用可打印的字符串比如冒号(:)来作定界符,将会很难分辨一个字符到底是数据还是定界符.而且还会限定你能用在数据项里面的字符数量,而这不是你想要的.一个通常的解决方法是,使用那些不经常使用的,不可打印的ASCII 码值来作为定界符,它们是非常完美的定界符,这样一来诸如冒号这样的可打印字符就可以解脱出来用在数据项中了.

6.7.2 三引号

虽然你可以用单引号或者双引号来定义字符串,但是如果你需要包含诸如换行符这样的特殊字符时,单引号或者双引号就不是那么方便了。Python 的三引号就是为了解决这个的,它允许一个字符串跨多行,字符串中可以包含换行符、制表符以及其他特殊字符.三引号的语法是一对连续的单引号或者双引号(通常都是成对的用):

>>> hi = '''hi there'''
>>> hi # repr()
'hi\nthere'
>>> print hi # str()
hi there

三引号让程序员从引号和特殊字符串的泥潭里面解脱出来,自始至终保持一小块字符串的格式是所谓的WYSIWYG(所见即所得)格式的。

一个典型的用例是,当你需要一块HTML 或者SQL 时,这时用字符串组合,特殊字符串转义将会非常的繁琐.

errHTML = '''
<HTML><HEAD><TITLE>
Friends CGI Demo</TITLE></HEAD>
<BODY><H3>ERROR</H3>
<B>%s</B><P>
<FORM><INPUT TYPE=button VALUE=Back
ONCLICK="window.history.back()"></FORM>
</BODY></HTML>
'''
cursor.execute('''
CREATE TABLE users (
login VARCHAR(8), uid INTEGER,
prid INTEGER)
''')


6.7.3 字符串不变性

Python 替你管理内存,每次你修改一个字符串或者做一些改变字符串内容的操作时,Python 都会自动为你分配一个新串.在下面的例子里面,Python 分别为"abc"和"def"分配了空间,当进行连接操作时,Python 自动为新的字符串"abcdef"分配了空间.

>>> 'abc' + 'def'
'abcdef'
给变量赋值是没什么不同:
>>> s = 'abc'
>>> s = s + 'def'
>>> s
'abcdef'

上面的例子里,看起来是我们先把"abc"赋给了s,然后在s 的末尾添加了"def".这样看起来字符串似乎是可变的,其实事实是在"s+'def""这个操作进行的时候,新建了一个新字符串,然后这个新的对象被赋给了s,原来的字符串'abc'被析构掉了.

我们可以用id()函数来更明显的显示出来到底发生了什么:

>> s = 'abc'
>>> id(s)
135060856
>>> s += 'def'
>>> id(s)
135057968

注意修改前后的身份是不同的.另一个测试是针对字符串的一个字符或者一个子串所做的修改.我们现在将展示对字符串的一个字符或者一片字符的改动都是不被允许的:

>>> s
'abcdef'
>>> s[2] = 'C'
Traceback (innermost last):
File "<stdin>", line 1, in ? AttributeError: __setitem__
>>> s[3:6] = 'DEF'
Traceback (innermost last):
File "<stdin>", line 1, in ?
AttributeError: __setslice__

两个操作都抛出了异常.为了实现要求,我们需要用现有字符串的子串来构建一个新串,然后把这个新串赋给原来的变量:

>>> s
'abcdef'
>>>
>>> s = '%sC%s' % (s[0:2], s[3:])
>>> s
'abCdef'
>>> s[0:3] + 'DEF'
'abCDEF'

对像字符串这样的不可变对象,左值必须是一个完整的对象,比如说一个字符串对象,不能是字符串的一部分.对赋值操作的右值没有这个限制.

6.8 Unicode

从Python1.6 起引进的Unicode 字符串支持,是用来在多种双字节字符的格式、编码进行转换的,其中包括一些对这类字符串的操作管理功能。内建的字符串和正则表达式对Unicode 字符串的支持,再加上string 模块的辅助,Python 已经可以应付大部分应用对Unicode 的存储、访问、操作的需要了。

6.8.1 术语





6.8.2 什么是Unicode?

Unicode 是计算机可以支持这个星球上多种语言的秘密武器.在Unicode 之前,用的都是ASCII,ASCII 码非常简单,每个英文字符都是以七位二进制数的方式存贮在计算机内,其范围是32 到126.当用户在文件中键入一个大写字符A 时,计算机会把A 的ASCII 码值65写入磁盘,然后当计算机读取该文件时,它会首先把65 转化成字符A 然后显示到屏幕上.ASCII 编码的文件小巧易读。一个程序只需简单地把文件的每个字节读出来,把对应的数值转换成字符显示出来就可以了.但是ASCII 字符只能表示95 个可打印字符.后来的软件厂商把ASCII 码扩展到了8 位,这样一来它就可以多标识128 个字符,可是223 个字符对需要成千上万的字符的非欧洲语系的语言来说仍然太少。Unicode 通过使用一个或多个字节来表示一个字符的方法突破了ASCII 的限制. 在这样机制下, Unicode 可以表示超过90,000 个字符.

6.8.3 你是怎么用Unicode 的?

早先,Python 只能处理8 位的ASCII 值,字符串就是简单的数据类型,为了处理一个字符串,用户必须首先创建一个字符串,然后把它作为参数传给string 模块的一个函数来处理.

2000年,Python1.6(和2.0)版释出,Unicode 第一次在Python 里面得到了支持.

为了让Unicode 和ASCII 码值的字符串看起来尽可能的相像,Python 的字符串从原来的简单数据类型改成了真正的对象.ASCII 字符串成了StringType,而Unicode 字符串成了UnicodeType 类型.它们的行为是非常相近的.string 模块里面都有相应的处理函数.string 模块已经停止了更新,只保留了ASCII 码的支持,string 模块已经不推荐使用,在任何需要跟Unicode 兼容的代码里都不要再用该模块,Python 保留该模块仅仅是为了向后兼容。

Python 里面处理Unicode 字符串跟处理ASCII 字符串没什么两样.Python 把硬编码的字符串叫做字面上的字符串,默认所有字面上的字符串都用ASCII 编码,可以通过在字符串前面加一个'u'前缀的方式声明Unicode 字符串,这个'u'前缀告诉Python 后面的字符串要编码成Unicode字符串 .

>>> "Hello World" # ASCII string
>>> u"Hello World" # Unicode string

内建的str()函数和chr()函数并没有升级成可以处理Unicode.它们只能处理常规的ASCII 编码字符串,如果一个Unicode 字符串被作作为参数传给了str()函数,它会首先被转换成ASCII 字符串然后在交给str()函数.如果该Unicode 字符串中包含任何不被ASCII 字符串支持的字符,会导致str()函数报异常.同样地,chr()函数只能以0 到255 作为参数工作.如果你传给它一个超出此范围的值(比如说一个Unicode 字符),它会报异常.新的内建函数unicode()和unichar()可以看成Unicode 版本的str()和chr().Unicode()函数可以把任何Python 的数据类型转换成一个Unicode 字符串,如果是对象,并且该对象定义了__unicode__()方法,它还可以把该对象转换成相应的Unicode 字符串.具体内容见6.1.3 和6.5.3 章节.

6.8.4 Codecs 是什么?

codec 是COder/DECoder 的首字母组合.它定义了文本跟二进制值的转换方式,跟ASCII 那种用一个字节把字符转换成数字的方式不同,Unicode 用的是多字节.这导致了Unicode 支持多种不同的编码方式. 比如说codec 支持的四种耳熟能详的编码方式是 :ASCII,ISO8859-1/Latin-1,UTF-8 和UTF-16.中最著名的是UTF-8 编码,它也用一个字节来编码ASCII 字符,这让那些必须同时处理ASCII码和Unicode 码文本的程序员的工作变得非常轻松,因为ASCII 字符的UTF-8 编码跟ASCII 编码完全相同。

UTF-8 编码可以用1 个到4 个字节来表示其他语言的字符,CJK/East 这样的东亚文字一般都是用3 个字节来表示,那些少用的、特殊的、或者历史遗留的字符用4 个字节来表示.这给那些需要直接处理Unicode 数据的程序员带来了麻烦,因为他们没有办法按照固定长度逐一读出各个字符.幸运的是我们不需要掌握直接读写Unicode 数据的方法,Python 已经替我们完成了相关细节,我们无须为处理多字节字符的复杂问ti而担心.Python 里面的其他编码不是很常用,事实上,我们认为大部分的Python 程序员根本就用不着去处理其他的编码,UTF-16 可能是个例外.

UTF-16 可能是以后大行其道的一种编码格式,它容易读写,因为它把所有的字符都是用单独的一个16 位字,两个字节来存储的,正因为此,这两个字节的顺序需要定义一下,一般的UTF-16 编码文件都需要一个BOM(Byte Order Mark),或者你显式地定义UTF-16-LE(小端)或者UTF-16-BE(大端)字节序.

从技术上讲,UTF-16 也是一种变长编码,但它不是很常用(人们一般不会知道或者根本不在意除了基本多文种平面BMP 之外到底使用的是那种平面),尽管如此,UTF-16 并不向后兼容ASCII,因此,实现它的程序很少,因为大家需要对ASCII 进行支持。

6.8.5 编码解码

Unicode 支持多种编码格式,这为程序员带来了额外的负担,每当你向一个文件写入字符串的时候,你必须定义一个编码(encoding 参数)用于把对应的Unicode内容转换成你定义的格式,Python 通过Unicode 字符串的encode()函数解决了这个问ti,该函数接受字符串中的字符为参数,输出你指定的编码格式的内容。

所以,每次我们写一个Unicode 字符串到磁盘上我们都要用指定的编码器给他"编码"一下,相应地,当我们从这个文件读取数据时,我们必须"解码"该文件,使之成为相应的Unicode 字符串对象.

简单的例子(uniFile.py):下面的代码创建了一个Unicode 字符串,用UTF-8 编码器将它编码,然后写入到一个文件中去.接着把数据从文件中读回来,解码成Unicode 字符串对象.最后,打印出Unicode 字符串,用以确认程序正确地运行.

CODEC = 'utf-8'
FILE = 'unicode.txt'

hello_out = u"Hello world\n" #创建了一个Unicode 字符串
bytes_out = hello_out.encode(CODEC) #指定的编码格式对其进行编码

f = open(FILE, 'w')
f.write(bytes_out)
f.close()

f = open(FILE, 'r')
bytes_in = f.read()
f.close()

hello_in = bytes_in.decode(CODEC) # 解码
print (hello_in,)

运行

$ unicode_example.py
Hello World

在文件系统中也会发现一个叫unicode.txt 的文件,里面包含跟输出的内容一致的数据.

$ cat unicode.txt
Hello World!

简单Web 例子:在第20 章Web 编程里面我们展示了一个简单的在CGI 应用中使用Unicode 的例子.

6.8.6 把Unicode 应用到实际应用中

这些处理Unicode 字符串的例子简单到让人感到有点假,事实上,只要你遵守以下的规则,处理Unicode 就是这么简单:

程序中出现字符串时一定要加个前缀 u.

不要用 str()函数,用unicode()代替.

不要用过时的 string 模块 -- 如果传给它的是非ASCII 字符,它会把一切搞砸。

不到必须时不要在你的程序里面编解码 Unicod 字符.只在你要写入文件或数据库或者网络时,才调用encode()函数;相应地,只在你需要把数据读回来的时候才调用decode()函数.

这些规则可以规避90%由于Unicode 字符串处理引起的bug.但是剩下的10%的问ti却让你处理不了,幸亏Python 提供了大量的模块、库来替你处理这些问ti.它们可以让你用10 行Python 语句写出其他语言需要100 行语句才能完成的功能,但是相应地,对Unicode 支持的质量也完全取决于这些模块、库.Python 标准库里面的绝大部分模块都是兼容Unicode 的.除了pickle 模块!pickle 模块只支持ASCII 字符串。如果你把一个Unicode 字符串交给pickle 模块来unpickle,它会报异常.

你必须先把你的字符串转换成ASCII 字符串才可以.所以最好是避免基于文本的pickle 操作.幸运地是现在二进制格式已经作为pickle 的默认格式了,pickle 的二进制格式支持不错.这点在你向数据库里面存东西是尤为突出,把它们作为BLOB 字段存储而不是作为TEXT 或者VARCHAR字段存储要好很多.万一有人把你的字段改成了Unicode 类型,这可以避免pickle 的崩溃.

如果你的程序里面用到了很多第三方模块,那么你很可能在各个模块统一使用Unicode 通讯方面遇到麻烦,Unicode 还没成为一项必须的规定,在你系统里面的第三方模块(包括你的应用要面对的平台\系统)需要用相同的Unicode 编码,否则,可能你就不能正确的读写数据.

作为一个例子,假设你正在构建一个用数据库来读写Unicode 数据的Web 应用.为了支持Unicode,你必须确保以下方面对Unicode 的支持:

􀁺 数据库服务器(MySQL,PostgreSQL,SQL Server,等等)

􀁺 数据库适配器(MySQLdb 等等)

􀁺 Web 开发框架(mod_python,cgi,Zope,Plane,Django 等等)

数据库方面最容易对付,你只要确保每张表都用UTF-8 编码就可以了。

数据库适配器可能有点麻烦,有些适配器支持Unicode 有些不支持,比如说MySQLdb,它并不是默认就支持Unicode 模式,你必须在connect()方法里面用一个特殊的关键字use_unicode来确保你得到的查询结果是Unicode 字符串.

mod_python 里面开启对Unicode 的支持相当简单, 只要在request 对象里面把text-encoding 一项设成"utf-8"就行了,剩下的mod_python 都会替你完成,Zope 等其他复杂的系统可能需要更多的工作来支持Unicode.

6.8.7 从现实中得来的教训

失误 #1: 你必须在一个极有限的时间内写出一个大型的应用,而且需要其他语言的支持,但是产品经理并没有明确定义这一点。你并没有kao虑Unicode 的兼容,直到项目快要结束... ,这时候再添加Unicode 的支持几乎不太可能,不是吗?

结果 #1: 没能预测到最终用户对其他语言界面的需求,在集成他们用的面向其他语种的应用时又没有使用Unicode 支持.更新整个系统既让让人觉得枯燥和更是浪费时间。

失误 #2:在源码中到处使用string 模块或者str()和chr()函数.

结果 #2:通过全局的查找替换把str()和chr()替换成unicode()和unichr(),但是这样一来很可能就不能再用pickle 模块,要用只能把所有要pickle 处理的数据存成二进制形式,这样一来就必须修改数据库的结构,而修改数据库结构就意味着全部推倒重来.

失误 #3: 不能确定所有的辅助系统都完全地支持Unicode.

结果 #3: 不得不去为那些系统打补丁,而其中有些系统可能你根本就没有源码.修复对Unicode 支持的bug 可能会降低代码的可靠性,而且非常有可能引入新的bug.

总结: 使应用程序完全支持Unicode,兼容其他的语言本身就是一个工程.

它需要详细的kao虑、计划.所有涉及到的软件、系统都需要检查,包括Python 的标准库和其他将要用到的第三方扩展模块.你甚至有可能需要组建一个经验丰富的团队来专门负责国际化(I18N)问ti。

6.8.8 Python 的Unicode 支持

内建的unicode()函数

Unicode 的工厂方法,同Unicode 字符串操作符(u / U)的工作方式很类似,它接受一个string 做参数,返回一个Unicode 字符串.

内建的decode()/encode()方法

decode()和encode()内建函数接受一个字符串做参数返回该字符串对应的解码后/编码后的字符串.decode()和encode()都可以应用于常规字符串和Unicode 字符串.decode()方法是在Python2.2 以后加入的.

Unicode 类型

Unicode 字符串对象是basestring 的子类、用Unicode()工厂方法或直接在字符串前面加一个u 或者U 来创建实例.支持Unicode 原始字符串,只要在你的字符串前面加一个ur 或者UR就可以了.

Unicode 序数

标准内建函数ord()工作方式相同,最近已经升级到可以支持Unicode 对象了。内建的unichr()函数返回一个对应的Unicode 字符(需要一个32 位的值);否则就产生一个ValueError异常.

强制类型转换

混合类型字符串操作需要把普通字符串转换成Unicode 对象.

异常

UnicodeError 异常是在exceptions 模块中定义的,ValueError 的子类.所有关于Unicode编解码的异常都要继承自UnicodeError.详见encode()函数.

标准编码

表6.9 简洁地列出了Python 中常用的编码方式.更详细、完全的列表见Python 的文档,下面是它的链接:

http://docs.python.org/lib/standard-encodings.html

RE 引擎对Unicode 的支持

正则表达式引擎需要Unicode 支持.详见6.9 节的re 模块.

表6.9 常用Unicode 编辑码

编码 描述

utf-8 变量长度为8 的编码(默认编码)

utf-16 变量长度为16 的编码(大/小端)

utf-16-le 小端UTF-16 编码

utf-16-be 大端UTF-16 编码

ascii 7-bit 7 位ASCII 码表

iso-8859-1 ISO 8859-1 (Latin-1) 码表

unicode-escape (定义见Python Unicode 构造函数)

raw-unicode-escape (定义见Python Unicode 构造函数)

native Python 用的内部格式

字符串格式化操作符

对于Python 的格式化字符串的操作符,%s 把Python 字符串中的Unicode 对象执行了str(u)操作,所以,输出的应该是u.encode(默认编码).如果格式化字符串是Unicode 对象,所有的参数都将首先强制转换成Unicode 然后根据对应的格式串一起进行格式转换.数字首先被转换成普通字符串, 然后在转换成Unicode.Python 字符串通过默认编码格式转化成Unicode.Unicode 对象不变,所有其他格式字符串都需要像上面这样转化,下面是例子:

u"%s %s" % (u"abc", "abc")   u"abc abc"

6.9 相关模块

表6.10 列出了Python 标准库里面与字符串有关的主要模块.



核心模块: re

正则表达式(RE)提供了高级的字符串模式匹配方法.通过描述这些模式的语法,你可以像使用“过滤器”一样高效地查找传进来的文本。这些过滤器允许你基于自定义的模式字符串抽取匹配模式、执行查找-替换或分割字符串.

re全面采用了Perl 正则表达式语法,使得Python 在对正则表达式的支持方面前进了一大步. Python1.6 里面重写了正则表达式引擎(SRE),增加了对Unicode 字符串的支持并对性能进行了重大的升级.SRE 引擎取代了原有正则表达式的模块下的PCRE 引擎.

该模块中包含的关键函数有:

compile() - 将一个RE 表达式编译成一个可重用的RE 对象;

match() - 试图从字符串的开始匹配一个模式;

search() - 找出字符串中所有匹配的项;

sub() - 进行查找替换操作。其中的一些函数返回匹配到的对象,你可以通过组匹配来访问(如果找到的话)。

15 章的整章内容都是讲述正则表达式。

6.10 字符串关键点总结

一些引号分隔的字符

你可以把字符串看成是Python 的一种数据类型,在Python 单引号或者双引号之间的字符数组或者是连续的字符集合.字符串的实际内容是这些单引号(')或者双引号(")之间的字符,不包括引号本身.

可以用两种引号来创建字符串是很有益处的,因为是当你的字符串中包含单引号时,如果用单引号创建字符串,那么字符串中的双引号就不需要转义。反之亦然.

不可分字符类型

字符串是唯一的字面上的字符序列类型.不过,字符本身并不是一种类型,所以,字符串是字符存储操作的最基本单位.字符应该视为长度为1 的字符串.

字符串格式化操作符 ( % )提供类似于printf()那样的功能.

字符串格式化操作符(见6.4.1 节)提供了一种基于多种输入类型的创建自定义字符串的灵活方式.它也提供了类似于C/C++世界里的格式化操作的接口.

三引号

在三引号字符串中可以包含诸如换行回车或者tab 键这样的特殊字符.三引号字符串是用两边各三个单引号(''')或者两边各三个双引号(""")来定义的.

原始字符串对每个特殊字符串都使用它的原意

原始字符串并不通过反斜线转义特殊字符的特性.这个特性使得原始字符串非常适用于那些需要字符串原意的场合,比如在定义一个正则表达式时.

Python 字符串不是通过NUL 或者'\0'来结束的

C 编程的一个主要问ti是你访问了一个字符串后面的本不属于你的空间,这种情况发生在你没有在字符串末尾添加终结符,NUL 或者'\0'(ASCII 值为0)的时候.Python 不仅为你自动管理内存,而且也把C 的这个负担或者说是小麻烦去掉了.Python 中的字符串不是以NUL 结束的,所以你不需要为是否已经添加终结符担心.字符串中只包含你所定义的东西,没有别的.

6.11 列表

像字符串类型一样,列表类型也是序列式的数据类型,可以通过下标或者切片操作来访问某一个或者某一块连续的元素.然而,相同的方面也就这些,字符串只能由字符组成,而且是不可变的(不能单独改变它的某个值),而列表则是能保留任意数目的Python 对象的灵活的容器。

列表不仅可以包含Python 的标准类型,而且可以用用户定义的对象作为自己的元素.列表可以包含不同类型的对象,而且要比C 或者Python 自己的数组类型(包含在array 扩展包中)都要灵活。列表可以执行pop,empt,sort,reverse 等操作.列表也可以添加或者减少元素.还可以跟其他的列表结合或者把一个列表分成几个.可以对单独一个元素或者多个元素执行insert,update,或者remove 操作.

元组类型在很多操作上都跟列表一样,许多用在列表上的例子在元组上照样能跑,它们的主要不同在于元组是不可变的,或者说是只读的,所以那些用于更新列表的操作,比如用切片操作来更新一部分元素的操作,就不能用于元组类型.

如何创建列表类型数据并给它赋值

创建一个列表就像给一个变量赋值一样的简单.你手工写一个列表(空的或者有值的都行)然后赋给一个变量,列表是由方括号([])来定义的,当然,你也可以用工厂方法来创建它.

>>> aList = [123, 'abc', 4.56, ['inner', 'list'], 7-9j]
>>> anotherList = [None, 'something to see here']
>>> print aList
[123, 'abc', 4.56, ['inner', 'list'], (7-9j)]
>>> print anotherList
[None, 'something to see here']
>>> aListThatStartedEmpty = []
>>> print aListThatStartedEmpty
[]
>>> list('foo')
['f', 'o', 'o']

如何访问列表中的值

列表的切片操作就像字符串中一样;切片操作符([])和索引值或索引值范围一起使用

如何更新列表

你可以通过在等号的左边指定一个索引或者索引范围的方式来更新一个或几个元素,你也可以用append()方法来追加元素到列表中去.

>>> aList
[123, 'abc', 4.56, ['inner', 'list'], (7-9j)]
>>> aList[2] = 'float replacer'
>>> aList
[123, 'abc', 'float replacer', ['inner', 'list'], (7-9j)]
>>> anotherList.append("hi, i'm new here")
>>> print anotherList
[None, 'something to see here', "hi, i'm new here"]
>>> aListThatStartedEmpty.append('not empty anymore')
>>> print aListThatStartedEmpty
['not empty anymore']

如何删除列表中的元素或者列表(本身)

要删除列表中的元素,如果你知道要删除元素的素引可以用del 语句,否则可以用remove()方法.

>>> aList
[123, 'abc', 'float replacer', ['inner', 'list'], (7-9j)]
>>> del aList[1]
>>> aList
[123, 'float replacer', ['inner', 'list'], (7-9j)]
>>> aList.remove(123)
>>> aList
['float replacer', ['inner', 'list'], (7-9j)]

你还可以通过pop()方法来删除并从列表中返回一个特定对象.

一般来说,程序员不需要去删除一个列表对象。列表对象出了作用域(比如程序结束,函数调用完成等等)后它会自动被析构,但是如果你想明确的删除一整个列表,你可以用del 语句:

del aList


6.12 操作符

6.12.1 标准类型操作符

在第4 章里,我们介绍了一些适用于包括标准类型在内的大部分对象的操作符,现在我们来看一下这些操作符如何作用在列表上:

>>> list1 = ['abc', 123]
>>> list2 = ['xyz', 789]
>>> list3 = ['abc', 123]
>>> 1ist1 < list2
True
>>> list2 < list3
False
>>> list2 > list3 and list1 == list3
True

比较运算符用在列表上时就不是那么简单了。比较列表时也是用的内建的cmp()函数,基本的比较逻辑是这样的:两个列表的元素分别比较,直到有一方的元素胜出,

比如我们上面的例子,'abc'和'xyz'的比较直接决定了比较结果,在'abc'<'xyz'时,list1<list2,list2>=list3,元组类型在进行比较操作时跟列表遵循相同的逻辑.

6.12.2 序列类型操作符

切片([] 和[:])

列表的切片操作跟字符串的切片操作很像,不过列表的切片操作返回的是一个对象或者是几个对象的集合,而不是像字符串那样,返回一个字符或者一个子串.我们定义以下几个列表用来做例子:

>>> num_list = [43, -1.23, -2, 6.19e5]
>>> str_list = ['jack', 'jumped', 'over', 'candlestick']
>>> mixup_list = [4.0, [1, 'x'], 'beef', -1.9+6j]

列表的切片操作也遵从正负索引规则,也有开始索引值,结束索引值,如果这两个值为空,默认也会分别指到序列的开始和结束位置.

>>> num_list[1]
-1.23
>>> num_list[1:]
[-1.23, -2, 619000.0]
>>> num_list[2:-1]
[-2]
>>>
>>> str_list[2]
'over'
>>> str_list[:2]
['jack', 'jumped']
>>> mixup_list
[4.0, [1, 'x'], 'beef', (-1.9+6j)]
>>> mixup_list[1]
[1, 'x']

跟字符串类型只能用字符为元素不同,列表类型的元素可以是另一个序列类型,意味着在列表的元素上也可以使用所有的序列操作符或者在其之上执行序列类型内建的各种操作.在下面的例子中,我们将会展示,不仅可以在一个切片操作的结果之上再进行切片,而且还可以改变这个切片的结果,即使新对象的类型跟原对象不同也可以.这跟多维数组有一些类似.

>>> mixup_list[1][1]
'x'
>>> mixup_list[1][1] = -64.875
>>> mixup_list
[4.0, [1, -64.875], 'beef', (-1.9+6j)]
这时用num_list 来做的另一个例子:
>>> num_list
[43, -1.23, -2, 6.19e5]
>>> num_list[2:4] = [16.0, -49]
>>> num_list
[43, -1.23, 16.0, -49]
>>> num_list[0] = [65535L, 2e30, 76.45-1.3j]
>>> num_list
[[65535L, 2e+30, (76.45-1.3j)], -1.23, 16.0, -49]

注意在最后一个例子中,是如何把列表的单一元素替换成一个列表.在列表中进行诸如remove,add,和replace 的操作是多么的自由了吧!还有一点要注意,如果你想以子列表的形式得到一个列表中的一个切片,那需要确保在赋值时等号的左边也是一个列表而不是一个列表的元素.

成员关系操作( in ,not in)

列表中(同样适用于元组),我们可以检查一个对象是否是一个列表(或者元组)的成员.

>>> mixup_list
[4.0, [1, 'x'], 'beef', (-1.9+6j)]
>>> 'beef' in mixup_list
True
>>> 'x' in mixup_list
False
>>> 'x' in mixup_list[1] True
>>> num_list
[[65535L, 2e+030, (76.45-1.3j)], -1.23, 16.0, -49]
>>> -49 in num_list
True
>>> 34 in num_list
False
>>> [65535L, 2e+030, (76.45-1.3j)] in num_list
True

连接接操作符( + )

连接操作符允许把多个列表对象合并在一起.注意,‍列表类型的连接操作也只能在同类型之间进行,换句话说,你不能把两个不同类型的对象(比如:[1,2]+'a')连接在一起,即便他们都是序列类型也不行.‍

>>> num_list = [43, -1.23, -2, 6.19e5]
>>> str_list = ['jack', 'jumped', 'over', 'candlestick']
>>> mixup_list = [4.0, [1, 'x'], 'beef', -1.9+6j]
>>> num_list + mixup_list
[43, -1.23, -2, 619000.0, 4.0, [1, 'x'], 'beef', (-1.9+6j)]
>>> str_list + num_list
['jack', 'jumped', 'over', 'candlestick', 43, -1.23, -2, 619000.0]

用extend()方法来代替连接操作符把一个列表的内容添加到另一个中去.使用extend()方法比连接操作的一个优点是它实际上是把新列表添加到了原有的列表里面,而不是像连接操作那样新建一个列表。

必须指出,连接操作符并不能实现向列表中添加新元素的操作.在接下来的例子中,我们展示了一个试图用连接操作向列表中添加新元素报错的例子.

>>> num_list + 'new item' Traceback (innermost last):
File "<stdin>", line 1, in ?
TypeError: illegal argument type for built-in operation

错误是因为我们在连接操作符的左右两边使用了不同类型的值.显然,我们的初衷是把一个字符串作为一个新元素添加到列表中去,不过方法不正确.我们有一个正确的方法:

使用内建函数append() (我们会在6.13 节里面正是地介绍append()和其他内建函数)

>>> num_list.append('new item')

重复操作符( * )

重复操作符可能更多的应用在字符串类型中,不过,列表和元组跟字符串同属序列类型,所以需要的时候也可以使用这一操作.

>>> num_list * 2
[43, -1.23, -2, 619000.0, 43, -1.23, -2, 619000.0]
>>>
>>> num_list * 3
[43, -1.23, -2, 619000.0, 43, -1.23, -2, 619000.0, 43,
-1.23, -2, 619000.0]

Python2.0 起,也开始支持复合赋值运算:

>>> hr = '-'
>>> hr *= 30
>>> hr
'------------------------------'


6.12.3 列表类型操作符和列表解析

其实Python 中没有专门用于列表类型的操作符.列表可以使用大部分的对象和序列类型的操作符.此外,列表类型有属于自己的方法.列表才有的构建--列表解析.这种方法是结合了列表的方括弧和for 循环,在逻辑上描述要创建的列表的内容.我们在第八章讨论列表解析,这里仅仅向本章其他地方所做的那样,展示一个简单的例子:

>>> [ i * 2 for i in [8, -2, 5] ]
[16, -4, 10]
>>> [ i for i in range(8) if i % 2 == 0 ]
[0, 2, 4, 6]


6.13 内建函数

6.13.1 标准类型函数

cmp()

我们还不知道cmp()函数是如何跟其他的比如列表和元组类型合作的,这些类型不仅含有数字和字符串,而且还有列表,元组,字典之类的其他对象,甚至可以是用户自定义的对象.这种情况下cmp()函数是如何工作的呢?

>>> list1, list2 = [123, 'xyz'], [456, 'abc']
>>> cmp(list1, list2)
-1
>>> cmp(list2, list1)
1
>>> list3 = list2 + [789]
>>> list3
[456, 'abc', 789]
>>> cmp(list2, list3)
-1

对于序列类型,比较操作稍微有点复杂了,比如当两个对象没有关系时或者两种类型根本就没有用于比较的函数时,这时Python 只能根据"逻辑"来做出结论.

除了这种极端的情况之外,安全又健全的比较方法是:如果有不相等的情况出现,比较操作就结束.这种算法是如何工作的呢?

列表的元素是可以无限迭代的.如果它的元素都是相同类型,则用标准的比较方法来作比较.否则,如果要比较的元素类型不一致,那么要得到一个准确的或者说绝对的比较结果就有些冒险.

当我们较list1 和list2 时,list1 和list2 进行逐项比较.第一个比较操作发生在两个列表的第一个元素之间,比如说,123 跟456 比较,因为123<456,所以list1 被认为小于list2.

如果比较的值相等,那么两个序列的下一个值继续比较,直到不相等的情况出现,或者到达较短的一个序列的末尾,在这种情况下,长的序列被认为是"较大"的.这就是为什么上面的list2<list3 的原因.元组类型比较也是用这种算法.最后我们以这种算法的关键点作为本节的结束:

1. 对两个列表的元素进行比较.

2. 如果比较的元素是同类型的,则比较其值,返回结果.

3. 如果两个元素不是同一种类型,则检查它们是否是数字.

a. 如果是数字,执行必要的数字强制类型转换,然后比较.

b. 如果有一方的元素是数字,则另一方的元素"大"(数字是"最小的")

c. 否则,通过类型名字的字母顺序进行比较.

4. 如果有一个列表首先到达末尾,则另一个长一点的列表"大".

5. 如果我们用尽了两个列表的元素而且所有元素都是相等的,那么结果就是个平局,就是说返回一个0.

6.13.2 序列类型函数

len()

对列表或者元组来说,返回列表或者元组的元素个数,容器里面的每个对象被作为一个项来处理:

>>> len(num_list)
4
>>> len(num_list*2)
8

max()和min()

max()和min()函数对列表和元组来说,它们被定义了很多的用处.比如对只包含数字和字符串对象的列表,max()和min()函数就非常有用,混合对象的结构越复杂返回的结构准确性就越差:

>>> max(str_list)
'park'
>>> max(num_list)
[65535L, 2e+30, (76.45-1.3j)]
>>> min(str_list)
'candlestick'
>>> min(num_list)
-49

sorted() and reversed()

>>> s = ['They', 'stamp', 'them', 'when', "they're", 'small']
>>> for t in reversed(s):
... print t,
...
small they're when them stamp They
>>> sorted(s)
['They', 'small', 'stamp', 'them', "they're", 'when']

注意字符串排序使用的是字典序,而不是字母序(字母'T'的ASCII 码值要比字母'a'的还要靠前)

enumerate() and zip()

>>> albums = ['tales', 'robot', 'pyramid']
>>> for i, album in enumerate(albums):
... print i, album
...
0 tales
1 robot
2 pyramid
>>> fn = ['ian', 'stuart', 'david']
>>> ln = ['bairnson', 'elliott', 'paton']
>>> for i, j in zip(fn, ln):
... print ('%s %s' % (i,j)).title()
...
Ian Bairnson Stuart Elliott David Paton

sum()

>>> a = [6, 4, 5]
>>> reduce(operator.add, a)
15
>>> sum(a)
15
>>> sum(a, 5)
20
>>> a = [6., 4., 5.]
>>> sum(a)
15.0

list() and tuple()

list()函数和tuple()函数接受可迭代对象(比如另一个序列)作为参数,并通过浅拷贝数据来创建一个新的列表或者元组.虽然字符串也是序列类型的,但是它们并不是经常用于list()和tuple(). 更多的情况下,它们用于在两种类型之间进行转换,比如你需要把一个已有的元组转成列表类型的(然后你就可以修改它的元素了),或者相反.

>>> aList = ['tao', 93, 99, 'time']
>>> aTuple = tuple(aList)
>>> aList, aTuple
(['tao', 93, 99, 'time'], ('tao', 93, 99, 'time'))
>>> aList == aTuple
False
>>> anotherList = list(aTuple)
>>> aList == anotherList
True
>>> aList is anotherList
False
>>> [id(x) for x in aList, aTuple, anotherList]
[10903800, 11794448, 11721544]

正如我们在本章的开头所讨论的,无论list()还是tuple()都不可能做完全的转换(见6.1.2 节).也就是说,传给tuple()的一个列表对象不可能变成一个元组,而你传给list()的对象也不可能真正的变成一个列表.虽然前后两个对象(原来的和新的对象)有着相同的数据集合(所以相等 == ),但是变量指向的却不是同一个对象了(所以执行 is 操作会返回false).还要注意,即使它们的所有的值都相同,一个列表也不可能"等于"一个元组.

6.13.3 列表类型内建函数

如果你不考虑range()函数的话,Python 中没有特定用于列表的内建函数.range()函数接受一个数值作为输入,输出一个符合标准的列表.第8 章里面详细讨论了range()函数.列表类型对象可以使用大多数的对象和序列的内建函数,并且,列表对象有属于它们自己的方法.

6.14 列表类型的内建函数

Python 中的列表类型有自己的方法.我们会在第13 章面向对象编程里面正式而详细的介绍方法这一概念,现在你只需要把方法视为特定对象的函数或者过程就好.本节讨论的方法就像内建的函数一样,除了它们只对列表类型进行操作之外.因为这些函数涉及到对列表更改(或者说更新),所以它们都不适应于元组.

列表的方法也是这样:list.method().我们用点号来访问一个对象的属性,然后用函数操作符( () )来调用这个方法.

可以在一个列表对象上应用dir()方法来得到它所有的方法和属性:

>>> dir(list) # or dir([])
['__add__', '__class__', '__contains__', '__delattr__',
'__delitem__', '__delslice__', '__doc__', '__eq__',
'__ge__', '__getattribute__', '__getitem__',
'__getslice__', '__gt__', '__hash__', '__iadd__',
'__imul__', '__init__', '__iter__', '__le__', '__len__',
'__lt__', '__mul__', '__ne__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__reversed__', '__rmul__',
'__setattr__', '__setitem__', '__setslice__', '__str__',
'append', 'count', 'extend', 'index', 'insert', 'pop',
'remove', 'reverse', 'sort']

表6.11 列出了目前列表类型支持的所有方法,稍后我们给出使用这些方法的例子.





>>> music_media = [45]
>>> music_media
[45]
>>> music_media.insert(0, 'compact disc')
>>> music_media
['compact disc', 45]
>>> music_media.append('long playing record')
>>> music_media
['compact disc', 45, 'long playing record']
>>> music_media.insert(2, '8-track tape')
>>> music_media
['compact disc', 45, '8-track tape', 'long playing record']

在这个例子中,向列表插入元素,或在尾部追加新的元素后,都会去检查这个列表.现在确认一下一个值是否在我们的列表中,并看看如何找出元素在列表中的索引值.我们用in 操作符和index()方法实现这两个需求.

>>> 'cassette' in music_media
False
>>> 'compact disc' in music_media
True
>>> music_media.index(45)
1
>>> music_media.index('8-track tape')
2
>>> music_media.index('cassette')
Traceback (innermost last):
File "<interactive input>", line 0, in ?
ValueError: list.index(x): x not in list

看起来用index()来检查一个元素是否存在于一个list中并不是个好主意,因为我们出错了.应该先用in 成员关系操作符(或者是not in)检查一下,然后在用index()找到这个元素的位置:

for eachMediaType in (45, '8-track tape', 'cassette'):
if eachMediaType in music_media:
print music_media.index(eachMediaType)

稍后我们将会发现该如何处理这种错误,而不是这样的一出错,程序就崩溃了。

接下来测试sort()和reverse()方法,它们会把列表中的元素排序,然后翻转.

>>> music_media
['compact disc', 45, '8-track tape', 'long playing record']
>>> music_media.sort()
>>> music_media
[45, '8-track tape', 'compact disc', 'long playing record']
>>> music_media.reverse()
>>> music_media
['long playing record', 'compact disc', '8-track tape', 45]

核心笔记:那些可以改变对象值的可变对象的方法是没有返回值的

Python 初学者经常会陷入一个误区:调用一个方法就返回一个值.最明显的例子就是sort():

>>> music_media.sort()# 没有输出?
>>>

在使用可变对象的方法如sort(),extend()和reverse()的时候要注意,这些操作会在列表中原地执行操作,也就是说现有的列表内容会被改变,但是没有返回值!是的,与之相反,字符串方法确实有返回值:

>>> 'leanna, silly girl!'.upper()
'LEANNA, SILLY GIRL!'

温习一下,字符串是不可变的 -- 不可变对象的方法是不能改变它们的值的,所以它们必须返回一个新的对象.如果你确实需要返回一个对象,那么我们建议你看一下Python2.4 以后加入的reversed()和sorted()内建函数.

它们像列表的方法一样工作,不同的是它们可以用做表达式,因为它们返回一个对象.同时原来的那个列表还是那个列表,没有改变,而你得到的是一个新的对象.

回到sort()方法,它默认的排序算法是归并排序的衍生算法,时间复杂度是O(lg(n!)).可以通过源码查看它们的详情 --Objects/listobject.c,还有算法描述: Objects/listsort.txt.

extend()方法接受一个列表的内容然后把它的所有元素追加到另一个列表中去:

>>> new_media = ['24/96 digital audio disc', 'DVD Audio disc', 'Super Audio CD']
>>> music_media.extend(new_media)
>>> music_media
['long playing record', 'compact disc', '8-track tape',
45, '24/96 digital audio disc', 'DVD Audio disc', 'Super
Audio CD']

extend()方法的参数支持任何可迭代对象,通过可迭代对象,你能做更多有趣的事情,比如:

>>> motd = []
>>> motd.append('MSG OF THE DAY')
>>> f = open('/etc/motd', 'r')
>>> motd.extend(f)
>>> f.close()
>>> motd
['MSG OF THE DAY', 'Welcome to Darwin!\n']

1.5.2 中加入的pop()方法会从列表中把最后的或指定的元素返回调用者.我们会在6.15.1节和练习中看到pop()方法,

6.15 列表的特殊特性

6.15.1 用列表构建其他数据结构

列表有容器和可变的特性,这使得它非常灵活,用它来构建其他的数据结构不是件难事.我们马上能想到的是堆栈和队列.

堆栈

堆栈是一个后进先出(LIFO)的数据结构.在栈上"push"元素是个常用术语,意思是把一个对象添加到堆栈中。反之,要删除一个元素,你可以把它"pop"出堆栈。

例6.3展示了一个菜单驱动的程序,它实现了一个简单的、用于存储字符串的堆栈.

stack = []

def pushit():
stack.append(input("enter new string: ").strip())

def popit():
if len(stack) == 0:
print ('cannot pop from an empty stack!')
else:
print ('remove[',stack.pop(), ']')

def viewstack():
print (stack)

CMDS = {'u':pushit, 'o':popit, 'v':viewstack}

def showmenu():
pr="""p(U)sh p(O)p (V)iew (Q)uit Enter choice:"""

while True:
while True:
try:
choice = input(pr).strip()[0].lower()
except(EOFError, KeyboardInterrupt, IndexError):
choice='q'
print ('your choice',choice)

if choice not in 'uovq':
print ('invalid option, try again')
else:
break
if choice == 'q':
break
CMDS[choice]()

if __name__ == "__main__":
showmenu()

队列

队列是一种先进先出(FIFO)的数据类型,.新的元素通过"入队"的方式添加进队列的末尾,"出队"就是从队列的头部删除.下面的例子里面展示了这种操作,我们把上面的堆栈的例子进行以下改造就可以用列表实现了一个简单的队列.

print ('remove[',stack.pop(0), ']')
#其余都一样,只不过出来的时候出来第一个


6.16 元组

元组跟列表非常相近的另一种容器类型.元组和列表看起来不同的一点是元组用的是圆括号而列表用的是方括号。而功能上,元组和列表相比有一个很重要的区别,元组是一种不可变类型.正因为这个原因,元组能做一些列表不能做的事情... 用做一个字典的key.另外当处理一组对象时,这个组默认是元组类型.

由于元组类型跟列表类型有着非常多的共同之处,为了避免太多重复信息,我们会讲解元组和列表在应用于每一组操作符和内建函数上时的区别,然后讨论一下元组的不变性以及其他独特的特性.

如何创建一个元组并给它赋值

创建一个元组并给他赋值实际上跟创建一个列表并给它赋值完全一样,除了一点,只有一个元素的元组需要在元组分割符里面加一个逗号(,)用以防止跟普通的分组操作符混淆.不要忘了它是一个工厂方法!

>>> aTuple = (123, 'abc', 4.56, ['inner', 'tuple'], 7-9j)
>>> anotherTuple = (None, 'something to see here')
>>> print aTuple
(123, 'abc', 4.56, ['inner', 'tuple'], (7-9j))
>>> print anotherTuple
(None, 'something to see here')
>>> emptiestPossibleTuple = (None,)
>>> print emptiestPossibleTuple
(None,)
>>> tuple('bar')
('b', 'a', 'r')

如何访问元组中的值

元组的切片操作跟列表一样

如何更新元组

跟数字和字符串一样,元组也是不可变类型,就是说你不能更新或者改变元组的元素,在6.2和6.3.2 节里面,我们是通过现有字符串的片段再构造一个新字符串的方式解决的,对元组同样需要这样.

>>> aTuple = aTuple[0], aTuple[1], aTuple[-1]
>>> aTuple
(123, 'abc', (7-9j))
>>> tup1 = (12, 34.56)
>>> tup2 = ('abc', 'xyz')
>>> tup3 = tup1 + tup2
>>> tup3
(12, 34.56, 'abc', 'xyz')

如何移除一个元组的元素以及元组本身

删除一个单独的元组元素是不可能的,当然,把不需要的元素丢弃后, 重新组成一个元组是ok的.

要显示地删除一整个元组,只要用del 语句减少对象引用计数.当这个引用计数达到0 的时候,该对象就会被析构.记住,大多数时候,我们不需要显式的用del 删除一个对象,一出它的作用域它就会被析构,Python 编程里面用到显式删除元组的情况非常之少.

del aTuple


6.17 元组操作符和内建函数

6.17.1 标准类型操作符,序列类型操作符和内建函数.

元组的对象和序列类型操作符还有内建函数跟列表的完全一样.你仍然可以对元组进行切片操作,合并操作,以及多次拷贝一个元组,还可以检查一个对象是否属于一个元组,进行元组之间的比较等.

创建,重复,连接操作

>>> t = (['xyz', 123], 23, -103.4)
>>> t
(['xyz', 123], 23, -103.4)
>>> t * 2
(['xyz', 123], 23, -103.4, ['xyz', 123], 23, -103.4)
>>> t = t + ('free', 'easy')
>>> t
(['xyz', 123], 23, -103.4, 'free', 'easy')

成员关系操作,切片操作

>>> 23 in t
True
>>> 123 in t
False
>>> t[0][1]
123
>>> t[1:]
(23, -103.4, 'free', 'easy')

内建函数

>>> str(t)
(['xyz', 123], 23, -103.4, 'free', 'easy')
>>> len(t)
5
>>> max(t)
'free'
>>> min(t)
-103.4
>>> cmp(t, (['xyz', 123], 23, -103.4, 'free', 'easy'))
0
>>> list(t)
[['xyz', 123], 23, -103.4, 'free', 'easy']

操作符

>>> (4, 2) < (3, 5)
False
>>> (2, 4) < (3, -1)
True
>>> (2, 4) == (3, -1)
False
>>> (2, 4) == (2, 4)
True


6.17.2 元组类型操作符和内建函数,内建方法

像列表一样 元组也没有它自己专用的运算符和内建函数.上一节中描述的列表方法都跟列表对象的可变性有关,比如说排序,替换,添加等等,因为元组是不可变的,所以这些操作对元组来说就是多余的,这些方法没有被实现.

6.18 元组的特殊特性.

6.18.1 不可变性给元组带来了什么影响?

在好多地方使用到了"不可变性"这个单词,除了这个词的计算机学科定义和实现,从应用的角度来考虑,这个词的底线是什么?一个数据类型成为不可变的到底意味着什么?

在三个标准不可变类型里面--数字,字符串和元组字符串--元组是受到影响最大的,一个数据类型是不可变的,简单来讲,就意味着一旦一个对象被定义了,它的值就不能再被更新,除非重新创建一个新的对象.对数字和字符串的影响不是很大,因为它们是标量类型,当它们代表的值改变时,这种结果是有意义的,是按照你所想要的方式进行访问的,而对于元组,事情就不是这样了。

因为元组是容器对象,很多时候你想改变的只是这个容器中的一个或者多个元素,不幸的是这是不可能的,切片操作符不能用作左值进行赋值。这和字符串没什么不同,切片操作只能用于只读的操作。

不可变并不是坏事,比如我们把数据传给一个不了解的API 时,可以确保我们的数据不会被修改。同样地,如果我们操作从一个函数返回的元组,可以通过内建list()函数把它转换成一个列表.

6.18.2 元组也不是那么“不可变”

虽然元组是被定义成不可变的,但这并不影响它的灵活性。元组并不像我们想的那么不可变,其实元组几个特定的行为让它看起来并不像我们先前声称的那么不可变.

比如说,既然我们可以把字符串组合在一起形成一个大字符串。那么把元组组合在一起形成一个大的元组也没什么不对,所以,连接操作可用,这个操作一点都没有改变那些小元组:

>>> s = 'first'
>>> s = s + ' second'
>>> s
'first second'
>>> t = ('third', 'fourth')
>>> t
('third', 'fourth')
>>> t = t + ('fifth', 'sixth')
>>> t
('third', 'fourth', 'fifth', 'sixth')

同样的概念也适用于重复操作。重复操作只不过是多次复制同样的元素,再有,我们前面提到过可以用一个简单的函数调用把一个元组变成一个可变的列表。我们的最后一个特性可能会吓到你。你可以“修改”特定的元组元素,哇!这意味着什么?

虽然元组对象本身是不可变的,但这并不意味着元组包含的可变对象也不可变了。

>>> t = (['xyz', 123], 23, -103.4)
>>> t
(['xyz', 123], 23, -103.4)
>>> t[0][1]
123
>>> t[0][1] = ['abc', 'def']
>>> t
(['xyz', ['abc', 'def']], 23, -103.4)

在上面的例子中,虽然t 是一个元组类型变量,但是我们设法通过替换它的第一个元素(一个列表对象)的项来“改变”了它。我们替换了t[0][1],原来是个整数,我们把它替换成了一个列表对象 ['abc','def'].虽然我们只是改变了一个可变对象,但在某种意义上讲,我们也“改变”了我们的元组类型变量。

6.18.3 默认集合类型

所有的多对象的,逗号分隔的,没有明确用符号定义的,比如说像用方括号表示列表和用圆括号表示元组一样,等等这些集合默认的类型都是元组,下面是一个简单的示例:

>>> 'abc', -4.24e93, 18+6.6j, 'xyz'
('abc', -4.24e+093, (18+6.6j), 'xyz')
>>> x, y = 1, 2
>>> x, y
(1, 2)

所有函数返回的多对象(不包括有符号封装的)都是元组类型。注意,有符号封装的多对象集合其实是返回的一个单一的容器对象,比如:

def foo1():
return obj1, obj2, obj3
def foo2():
return [obj1, obj2, obj3]
def foo3():
return (obj1, obj2, obj3)

上面的例子中,foo1()返回3 个对象,默认的作为一个包含3 个对象的元组类型,foo2()返回一个单一对象,一个包含3 个对象的列表,还有foo3()返回一个跟foo1()相同的对象.唯一不同的是这里的元组是显式定义的.

为了避免令人讨厌的副作用,建议总是显式的用圆括号表达式表示元组或者创建一个元组.

>>> 4, 2 < 3, 5 # int, comparison, int
(4, True, 5)
>>> (4, 2) < (3, 5) # tuple comparison
False

在第一个例子中小于号的优先级高于逗号,2<3 的结果成了元组变量的第二个元素,适当的封装元组就会得到希望得到的结果.

6.18.4 单元素元组

曾经试过创建一个只有一个元素的元组?你在列表上试过,它可以完成,但是无论你怎么在元组上试验,你都不能得到想要的结果。

>>> ['abc']
['abc']
>>> type(['abc']) # a list
<type 'list'>
>>> ('xyz')
'xyz'
>>> type(('xyz')) # a string, not a tuple
<type 'str'>

或许你忘记了圆括号被重载了,它也被用作分组操作符。由圆括号包裹的一个单一元素首先被作为分组操作,而不是作为元组的分界符。一个变通的方法是在第一个元素后面添一个逗号(,)来表明这是一个元组而不是在做分组操作.

>>> ('xyz',)
('xyz',)


6.18.5 字典的关键字

不可变对象的值是不可改变的。这就意味着它们通过hash 算法得到的值总是一个值。这是作为字典键值的一个必备条件。在下一章节里面我们会讨论到,键值必须是可哈希的对象,元组变量符合这个标准,而列表变量就不行。

核心笔记:列表 VS 元组

"为什么我们要区分元组和列表变量?",一个原因是在有些情况下,使用其中的一种类型要优于使用另一种类型。

最好使用不可变类型变量的一个情况是:你在维护一些敏感的数据,并且需要把这些数据传递给一个并不了解的函数(或许是一个根本不是你写的API),作为一个只负责一个软件某一部分的工程师,如果你确信你的数据不会被调用的函数篡改,你会觉得安全了许多。

一个需要可变类型参数的例子是:你在管理动态数据集合时。你需要先把它们创建出来,逐渐地或者不定期的添加它们,或者有时还要移除一些单个的元素。这是一个必须使用可变类型对象的典型例子。幸运的是,通过内建的list()和tuple()转换函数,你可以非常轻松的在两者之间进行转换.

list()和tuple()函数允许你用一个列表来创建一个元组,反之亦然.如果你有一个元组变量,但你需要一个列表变量因为你要更新一下它的对象,这时list()函数就是你最好的帮手.如果你有一个列表变量,并且想把它传递给一个函数,或许一个API,而你又不想让任何人弄乱你的数据,这时tuple()函数就非常有用。

6.19 相关模块

表6.12 列出了与序列类型相关的关键模块,这个列表包含了前面我们间接提到的数组模块,它就像列表类型,不过它要求所有的元素都是同一类型。

Table 6.12 与序列类型相关的模块



operator 模块除了提供与数字操作符相同的功能外,还提供了与序列类型操作符相同的功能.types 模块是代表python 支持的全部类型的type 对象的引用。最后,UserList 模块包含了list 对象的完全的类实现。因为Python 类型不能作为子类,所以这个模块允许用户获得类似list 的类,也可以派生出新的类或功能。如果你熟悉面向对象编程的话,我们强烈推荐你阅读第13 章

6.20 拷贝Python 对象

浅拷贝和深拷贝

我们讲过对象赋值实际上是简单的对象引用。当你创建一个对象,然后把它赋给另一个变量的时候,Python 并没有拷贝这个对象,而是拷贝了这个对象的引用。

假设你想创建一对小夫妻的通用档an,名为person.然后你分别为他俩拷贝一份。

我们展示了两种拷贝对象的方式,一种使用了切片操作,另一种用了工厂方法,为了区分出三个不同的对象,我们使用id()内建函数来显示每个对象的标识符。(我们还可以用is 操作符来做相同的事情)

>>> person = ['name', ['savings', 100.00]]
>>> hubby = person[:] # slice copy
>>> wifey = list(person) # fac func copy
>>> [id(x) for x in person, hubby, wifey]
[11826320, 12223552, 11850936]

为他们创建了初始有$100 的个人存款帐户。用户名改为定制的名字。但是,当丈夫取走$50后,他的行为影响到了他妻子的账户,虽然我们进行了分开的拷贝,为什么会这样呢?

>>> hubby[0] = 'joe'
>>> wifey[0] = 'jane'
>>> hubby, wifey
(['joe', ['savings', 100.0]], ['jane', ['savings', 100.0]])
>>> hubby[1][1] = 50.00
>>> hubby, wifey
(['joe', ['savings', 50.0]], ['jane', ['savings', 50.0]])

原因是我们仅仅做了一个浅拷贝。对一个对象进行浅拷贝其实是新创建了一个类型跟原对象一样,其内容是原来对象元素的引用,换句话说,这个‍拷贝的对象本身是新的,但是它的内容不是‍.序列类型对象的浅拷贝是默认类型拷贝,并可以以下几种方式实施:

(1)完全切片操作[:]

(2)利用工厂函数,比如list(),dict()等

(3)使用copy 模块的copy 函数.

但当妻子的名字被赋值,为什么丈夫的名字没有受到影响?难道它们的名字现在不应该都是'jane'了吗?这是因为在这两个列表的两个对象中,第一个对象是不可变的(是个字符串类型),而第二个是可变的(一个列表).正因为如此,当进行浅拷贝时,字符串被显式的拷贝,并新创建了一个字符串对象,而列表元素只是把它的引用复制了一下,并不是它的成员.所以改变名字没有任何问ti,但是更改他们银行账号的任何信息都会引发问ti.现在,让我们分别看一下每个列表的元素的对象ID 值,注意,银行账号对象是同一个对象,这也是为什么对一个对象进行修改会影响到另一个的原因.

BEFORE:
>>> [id(x) for x in hubby]
[9919616, 11826320]
>>> [id(x) for x in wifey]
[9919616, 11826320]
AFTER:
>>> [id(x) for x in hubby]
[12092832, 11826320]
>>> [id(x) for x in wifey]
[12191712, 11826320]

假设我们要给这对夫妻创建一个联合账户,那这是一个非常棒的程序,但是,如果需要的是两个分离账户,就需要作些改动了.要得到一个完全拷贝或者说深拷贝--创建一个新的容器对象,包含原有对象元素(引用)全新拷贝的引用--需要copy.deepcopy()函数.我们使用深拷贝来重写整个例子.

>>> person = ['name', ['savings', 100.00]]
>>> hubby = person
>>> import copy
>>> wifey = copy.deepcopy(person)
>>> [id(x) for x in person, hubby, wifey]
[12242056, 12242056, 12224232]
>>> hubby[0] = 'joe'
>>> wifey[0] = 'jane'
>>> hubby, wifey
(['joe', ['savings', 100.0]], ['jane', ['savings', 100.0]])
>>> hubby[1][1] = 50.00
>>> hubby, wifey
(['joe', ['savings', 50.0]], ['jane', ['savings', 100.0]])

这就是我们想要的方式,作为验证,让我们确认一下所有四个对象都是不同的.

>>> [id(x) for x in hubby]
[12191712, 11826280]
>>> [id(x) for x in wifey]
[12114080, 12224792]

以下有几点关于拷贝操作的警告。第一,非容器类型(比如数字,字符串和其他"原子"类型的对象,像代码,类型和xrange 对象等)没有被拷贝一说,浅拷贝是用完全切片操作来完成的.第二,如果元组变量只包含原子类型对象,对它的深拷贝将不会进行.如果我们把账户信息改成元组类型,那么即便按我们的要求使用深拷贝操作也只能得到一个浅拷贝:

>>> person = ['name', ('savings', 100.00)]
>>> newPerson = copy.deepcopy(person)
>>> [id(x) for x in person, newPerson]
[12225352, 12226112]
>>> [id(x) for x in person]
[9919616, 11800088]
>>> [id(x) for x in newPerson]
[9919616, 11800088]

核心模块: copy

浅拷贝和深拷贝操作都可以在copy 模块中找到.其实copy 模块中只有两个函数可用:copy()进行浅拷贝操作,而deepcopy()进行深拷贝操作.

6.21 序列类型小结

序列类型为数据的顺序存储提供了几种机制.字符串是最常用的数据载体,无论是用于给用户显示,存贮到硬盘,通过网络传输,还是作为一个多源信息的容器.列表和元组提供了容器存储能力,允许简单的操作和访问多个对象,无论它们是Python 的对象还是用户自定义的对象.单一元素或一组元素可以通过持续有序地索引偏移进行切片操作来访问.总之,这些数据类型为你的Python 开发环境提供了灵活而易用的存贮工具.我们用表6.13--序列类型的操作符,内建函数和方法的摘要列表来总结本章.

Table 6.13 序列类型操作符,内建函数和方法





内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: