本章节深入讲述一些你已经学习过的东西,并且还加入了新的内容。
链表类型有很多方法,这里是链表类型的所有方法:
x) |
一个元素添加到链表的结尾,相当于 a[len(a):] = [x]
L) |
过添加指定链表的所有元素来扩充链表,相当于 a[len(a):] = L
。
i, x) |
在指定位置插入一个元素。第一个参数是准备插入到其前面的那个元素的索引,例如a.insert(0, x)
会插入到整个链表之前,而a.insert(len(a), x)
相当于 a.append(x)
。
x) |
除链表中值为x的第一个元素。如果没有这样的元素,就会返回一个错误。
[i]) |
从链表的指定位置删除元素,并将其返回。如果没有指定索引,a.pop()
返回最后一个元素。元素随即从链表中被删除。(方法中i两边的方括号表示这个参数是可选的,而不是要求你输入一对方括号,你会经常在Python 库参考手册中遇到这样的标记。)
x) |
回链表中第一个值为x的元素的索引。如果没有匹配的元素就会返回一个错误。
x) |
回x在链表中出现的次数。
) |
) |
倒排链表中的元素。
下面这个示例演示了链表的大部分方法:
>>> a = [66.6, 333, 333, 1, 1234.5] >>> print a.count(333), a.count(66.6), a.count('x') 2 1 0 >>> a.insert(2, -1) >>> a.append(333) >>> a [66.6, 333, -1, 333, 1, 1234.5, 333] >>> a.index(333) 1 >>> a.remove(333) >>> a [66.6, -1, 333, 1, 1234.5, 333] >>> a.reverse() >>> a [333, 1234.5, 1, 333, -1, 66.6] >>> a.sort() >>> a [-1, 1, 66.6, 333, 333, 1234.5]
链表方法使得链表可以很方便的做为一个堆栈来使用,堆栈作为特定的数据结构,最先进入的元素最后一个被释放(后进先出)。用append() 方法可以把一个元素添加到堆栈顶。用不指定索引的pop() 方法可以把一个元素从堆栈顶释放出来。例如:
>>> stack = [3, 4, 5] >>> stack.append(6) >>> stack.append(7) >>> stack [3, 4, 5, 6, 7] >>> stack.pop() 7 >>> stack [3, 4, 5, 6] >>> stack.pop() 6 >>> stack.pop() 5 >>> stack [3, 4]
你也可以把链表当做队列使用,队列作为特定的数据结构,最先进入的元素最先释放(先进先出)。使用 append()方法可以把元素添加到队列最后,以0为参数调用 pop() 方法可以把最先进入的元素释放出来。例如:
>>> queue = ["Eric", "John", "Michael"] >>> queue.append("Terry") # Terry arrives >>> queue.append("Graham") # Graham arrives >>> queue.pop(0) 'Eric' >>> queue.pop(0) 'John' >>> queue ['Michael', 'Terry', 'Graham']
对于链表来讲,有三个内置函数非常有用:filter(), map(), 和 reduce()。
"filter(function, sequence)"返回一个序列(sequence),包括了给定序列中所有调用function(item)
后返回值为true的元素。(如果可能的话,会返回相同的类型)。例如,以下程序可以计算部分素数:
>>> def f(x): return x % 2 != 0 and x % 3 != 0 ... >>> filter(f, range(2, 25)) [5, 7, 11, 13, 17, 19, 23]
"map(function, sequence)" 为每一个元素依次调用function(item)
并将返回值组成一个链表返回。例如,以下程序计算立方:
>>> def cube(x): return x*x*x ... >>> map(cube, range(1, 11)) [1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]
可以传入多个序列,函数也必须要有对应数量的参数,执行时会依次用各序列上对应的元素来调用函数(如果某些序列比其它的短,就用None
来代替)。如果把None做为一个函数传入,则直接返回参数做为替代。例如:
>>> seq = range(8) >>> def add(x, y): return x+y ... >>> map(add, seq, seq) [0, 2, 4, 6, 8, 10, 12, 14]"reduce(func, sequence)" 返回一个单值,它是这样构造的:首先以序列的前两个元素调用函数,再以返回值和第三个参数调用,依次执行下去。例如,以下程序计算1到10的整数之和:
>>> def add(x,y): return x+y ... >>> reduce(add, range(1, 11)) 55
如果序列中只有一个元素,就返回它,如果序列是空的,就抛出一个异常。
可以传入第三个参数做为初始值。如果序列是空的,就返回初始值,否则函数会先接收初始值和序列的第一个元素,然后是返回值和下一个元素,依此类推。例如:
>>> def sum(seq): ... def add(x,y): return x+y ... return reduce(add, seq, 0) ... >>> sum(range(1, 11)) 55 >>> sum([]) 0
不要像示例中这样定义sum():因为合计数值是一个通用的需求,在2.3版中,提供了内置的sum(sequence)
函数。
New in version 2.3.
链表推导式提供了一个创建链表的简单途径,无需使用map(), filter() 以及 lambda。返回链表的定义通常要比创建这些链表更清晰。每一个链表推导式包括在一个for 语句之后的表达式,零或多个 for或 if 语句。返回值是由 for 或 if子句之后的表达式得到的元素组成的链表。如果想要得到一个元组,必须要加上括号。
>>> freshfruit = [' banana', ' loganberry ', 'passion fruit '] >>> [weapon.strip() for weapon in freshfruit] ['banana', 'loganberry', 'passion fruit'] >>> vec = [2, 4, 6] >>> [3*x for x in vec] [6, 12, 18] >>> [3*x for x in vec if x > 3] [12, 18] >>> [3*x for x in vec if x < 2] [] >>> [[x,x**2] for x in vec] [[2, 4], [4, 16], [6, 36]] >>> [x, x**2 for x in vec] # error - parens required for tuples File "<stdin>", line 1, in ? [x, x**2 for x in vec] ^ SyntaxError: invalid syntax >>> [(x, x**2) for x in vec] [(2, 4), (4, 16), (6, 36)] >>> vec1 = [2, 4, 6] >>> vec2 = [4, 3, -9] >>> [x*y for x in vec1 for y in vec2] [8, 6, -18, 16, 12, -36, 24, 18, -54] >>> [x+y for x in vec1 for y in vec2] [6, 5, -7, 8, 7, -5, 10, 9, -3] >>> [vec1[i]*vec2[i] for i in range(len(vec1))] [8, 12, -54]
链表推导式比 map()更复杂,可调用多个参数和嵌套函数。
>>> [str(round(355/113.0, i)) for i in range(1,6)] ['3.1', '3.14', '3.142', '3.1416', '3.14159']
有一个方法可从链表中删除指定索引的元素:del 语句。这个方法也可以从链表中删除切片(之前我们是把一个空链表赋给切片)。例如:
>>> a = [-1, 1, 66.6, 333, 333, 1234.5] >>> del a[0] >>> a [1, 66.6, 333, 333, 1234.5] >>> del a[2:4] >>> a [1, 66.6, 1234.5]
del 也可以用于删除整个变量:
>>> del a
此后再引用这个名字会发生错误(至少要到给它赋另一个值为止)。后面我们还会发现del的其它用法。
我们知道链表和字符串有很多通用的属性,例如索引和切片操作。它们是序列类型中的两种。因为Python是一个在不停进化的语言,也可能会加入其它的
>>> t = 12345, 54321, 'hello!' >>> t[0] 12345 >>> t (12345, 54321, 'hello!') >>> # Tuples may be nested: ... u = t, (1, 2, 3, 4, 5) >>> u ((12345, 54321, 'hello!'), (1, 2, 3, 4, 5))
如你所见,元组在输出时总是有括号的,以便于正确表达嵌套结构。在输入时可能有或没有括号都可以,不过经常括号都是必须的(如果元组是一个更大的表达式的一部分)。
元组有很多用途。例如(x, y)坐标点,数据库中的员工记录等等。元组就像字符串,不可改变:不能给元组的一个独立的元素赋值(尽管你可以通过联接和切片来模仿)。也可以通过包含可变对象来创建元组,例如链表。
一个特殊的问题是构造包含零个或一个元素的元组:为了适应这种情况,语法上有一些额外的改变。一对空的括号可以创建空元组;要创建一个单元素元组可以在值后面跟一个逗号(在括号中放入一个单值是不够的)。丑陋,但是有效。例如:
>>> empty = () >>> singleton = 'hello', # <-- note trailing comma >>> len(empty) 0 >>> len(singleton) 1 >>> singleton ('hello',)
语句 t = 12345, 54321, 'hello!' 是元组封装(sequence packing)的一个例子:值 12345, 54321 和 'hello!' 被封装进元组。其逆操作可能是这样:
>>> x, y, z = t
这个调用被称为序列拆封非常合适。序列拆封要求左侧的变量数目与序列的元素个数相同。要注意的是可变参数(multiple assignment )其实只是元组封装和序列拆封的一个结合!
There is a small bit of asymmetry here: packing multiple values always creates a tuple, and unpacking works for any sequence.
这里有一点不对称:封装多重参数通常会创建一个元组,而拆封操作可以作用于任何序列。
另一个非常有用的Python内建数据类型是字典。字典在某些语言中可能称为“联合内存”(``associative memories'')或“联合数组”(``associative arrays'')。序列是以连续的整数为索引,与此不同的是,字典以关键字为索引,关键字可以是任意不可变类型,通常用字符串或数值。如果元组中只包含字符串和数字,它可以做为关键字,如果它直接或间接的包含了可变对象,就不能当做关键字。不能用链表做关键字,因为链表可以用它们的append() 和 extend()方法,或者用切片、或者通过检索变量来即时改变。
理解字典的最佳方式是把它看做无序的关键字:值 对(key:value pairs)集合,关键字必须是互不相同的(在同一个字典之内)。一对大括号创建一个空的字典:{}
。初始化链表时,在大括号内放置一组逗号分隔的关键字:值对,这也是字典输出的方式。
字典的主要操作是依据关键字来存储和析取值。也可以用 del
来删除关键字:值对。如果你用一个已经存在的关键字存储值,以前为该关键字分配的值就会被遗忘。试图析取从一个不存在的关键字中读取值会导致错误。
字典的 keys()方法返回由所有关键字组成的链表,该链表的顺序不定(如果你需要它有序,只能调用关键字链表的sort() 方法)。使用字典的 has_key()方法可以检查字典中是否存在某一关键字。
这是一个关于字典应用的小示例:
>>> tel = {'jack': 4098, 'sape': 4139} >>> tel['guido'] = 4127 >>> tel {'sape': 4139, 'guido': 4127, 'jack': 4098} >>> tel['jack'] 4098 >>> del tel['sape'] >>> tel['irv'] = 4127 >>> tel {'guido': 4127, 'irv': 4127, 'jack': 4098} >>> tel.keys() ['guido', 'irv', 'jack'] >>> tel.has_key('guido') True
链表中存储关键字-值对元组的话,字典可以从中直接构造。关键字-值对来自一个模式时,可以用链表推导式简单的表达关键字-值链表。
>>> dict([('sape', 4139), ('guido', 4127), ('jack', 4098)]) {'sape': 4139, 'jack': 4098, 'guido': 4127} >>> dict([(x, x**2) for x in vec]) # use a list comprehension {2: 4, 4: 16, 6: 36}
在字典中循环时,关键字和对应的值可以使用 iteritems()方法同时解读出来。
>>> knights = {'gallahad': 'the pure', 'robin': 'the brave'} >>> for k, v in knights.iteritems(): ... print k, v ... gallahad the pure robin the brave
在序列中循环时,索引位置和对应值可以使用enumerate()函数同时得到。
>>> for i, v in enumerate(['tic', 'tac', 'toe']): ... print i, v ... 0 tic 1 tac 2 toe
同时循环两个或更多的序列,可以使用 zip() 整体解读。
>>> questions = ['name', 'quest', 'favorite color'] >>> answers = ['lancelot', 'the holy grail', 'blue'] >>> for q, a in zip(questions, answers): ... print 'What is your %s? It is %s.' % (q, a) ... What is your name? It is lancelot. What is your quest? It is the holy grail. What is your favorite color? It is blue.
用于 while
和 if
语句的条件包括了比较之外的操作符。
in
和 not in
比较操作符审核值是否在一个区间之内。操作符 is
is not
和比较两个对象是否相同;这只和诸如链表这样的可变对象有关。所有的比较操作符具有相同的优先级,低于所有的数值操作。
比较操作可以传递。例如 a < b == c
审核是否 a
小于 b
并 b
等于c
。
比较操作可以通过逻辑操作符 and
和 or
组合,比较的结果可以用 not
来取反义。这些操作符的优先级又低于比较操作符,在它们之中,not
具有最高的优先级, or
优先级最低,所以A and not B or C
等于 (A and (not B)) or C
。当然,表达式可以用期望的方式表示。
逻辑操作符 and
和 or
也称作短路操作符:它们的参数从左向右解析,一旦结果可以确定就停止。例如,如果 A
和 C
为真而 B
为假, A and B and C
不会解析 C
。作用于一个普通的非逻辑值时,短路操作符的返回值通常是最后一个变量
可以把比较或其它逻辑表达式的返回值赋给一个变量,例如:
>>> string1, string2, string3 = '', 'Trondheim', 'Hammer Dance' >>> non_null = string1 or string2 or string3 >>> non_null 'Trondheim'
需要注意的是Python与C不同,在表达式内部不能赋值。C 程序员经常对此抱怨,不过它避免了一类在 C 程序中司空见惯的错误:想要在解析式中使 ==
时误用了 =
操作符。
序列对象可以与相同类型的其它对象比较。比较操作按 字典序 进行:首先比较前两个元素,如果不同,就决定了比较的结果;如果相同,就比较后两个元素,依此类推,直到所有序列都完成比较。如果两个元素本身就是同样类型的序列,就递归字典序比较。如果两个序列的所有子项都相等,就认为序列相等。如果一个序列是另一个序列的初始子序列,较短的一个序列就小于另一个。字符串的字典序按照单字符的 ASCII 顺序。下面是同类型序列之间比较的一些例子:
(1, 2, 3) < (1, 2, 4) [1, 2, 3] < [1, 2, 4] 'ABC' < 'C' < 'Pascal' < 'Python' (1, 2, 3, 4) < (1, 2, 4) (1, 2) < (1, 2, -1) (1, 2, 3) == (1.0, 2.0, 3.0) (1, 2, ('aa', 'ab')) < (1, 2, ('abc', 'a'), 4)
需要注意的是不同类型的对象比较是合法的。输出结果是确定而非任意的:类型按它们的名字排序。因而,一个链表(list)总是小于一个字符串(string),一个字符串(string)总是小于一个元组(tuple)等等。数值类型比较时会统一它们的数据类型,所以0等于0.0,等等。5.2