《Python官方文档》5.数据结构

数据结构

本章会更细节的讲一些你已经学过的东西,同时也会加一些新的内容。

5.1 List有关方法

list有许多方法,以下是list方法的所有实例: list.append(x) 添加1个item到list的末尾,等同于a[len(a):] = [x] list.extend(iterable) 以迭代器方式追加的所有item拓展list,等同于a[len(a):] = iterable list.insert(x) 把x插入到list的指定位置。第一个参数是插入的位置,a.insert(0, x)插入到list头, a.insert(len(a), x)相当于a.append(x)list.remove(x) 移除list中第一个等于x的item。如果没有这个item会报错 list.pop([i]) 移除list中指定位置的item,同时返回item。如果位置没有指定,a.pop()'会移除list的最后1个item,并返回它。中括号中的参数x是可选的,中括号不是必须的。你在Python手册中会经常看见这种用法。 list.clear() 清空list,等同于del a[:] list.index(x[, start[, end]]) 返回第一个等于x的item的序列位置,是从0开始算。如果没有等于x的item方法会报ValueError。 可选参数作为切片使用,定义了起始和结束位置。返回的位置是相对于原先完整的list而不是起始参数。 list.count(x) 返回list中x出现的次数list.sort(key=None, reverse=False) 本地对list排序(参数是拿来定义sort,去sorted()看具体的解释吧) list.reverse() 不需要额外空间,倒排list。 list.copy() 返回list的浅拷贝。相当于a[:]`

以下实例使用了上述大部分方法: fruits = ['orange', 'apple', 'pear', 'banana', 'kiwi', 'apple', 'banana'] fruits.count('apple') 2 fruits.count('tangerine') 0 fruits.index('banana') 3 fruits.index('banana', 4) # Find next banana starting a position 4 6 fruits.reverse() fruits ['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange'] fruits.append('grape') fruits ['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange', 'grape'] fruits.sort() fruits ['apple', 'apple', 'banana', 'banana', 'grape', 'kiwi', 'orange', 'pear'] fruits.pop() 'pear' 你也许注意到,像insert, remove, sort方法仅修改list本身 仅返回None。这是Python 可变数据结构的设计哲学。

5.1.1 像栈一样使用list

list这些方法使得list非常容易像栈一样被使用,最后1个插入的元素首先被取出,添加到栈顶使用append();获取栈顶元素使用pop(),不需要特别指定次序:

fruits = ['orange', 'apple', 'pear', 'banana', 'kiwi', 'apple', 'banana']
fruits.count('apple')
2
fruits.count('tangerine')
0
fruits.index('banana')
3
fruits.index('banana', 4) # Find next banana starting a position 4
6
fruits.reverse()
fruits
['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange']
fruits.append('grape')
fruits
['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange', 'grape']
fruits.sort()
fruits
['apple', 'apple', 'banana', 'banana', 'grape', 'kiwi', 'orange', 'pear']
fruits.pop()
'pear'

5.1.2 像队列一样使用list

当然也可以把list作为队列,先进先出,但是,作为队列list并不高效。因为从list尾部追加删除原色很快,但是从list头做同样操作就很慢了,因为其他所有元素都要移动1次。 真要实现队列,请使用collections.deque这个专门的数据结构,它2端插入提取都很快:

from collections import deque
queue = deque(["Eric", "John", "Michael"])
queue.append("Terry") # Terry arrives
queue.append("Graham") # Graham arrives
queue.popleft() # The first to arrive now leaves
'Eric'
queue.popleft() # The second to arrive now leaves
'John'
queue # Remaining queue in order of arrival
deque(['Michael', 'Terry', 'Graham'])

5.1.3 List 列表解析式

list 的列表解析式提供了一种简洁的创建list的方法。在以下场景我们常使用list列表解析式:生成1个新的list,它的元素是有原始序列数据结构中取出每个元素操作;或者产生原始序列结构的子集。 比如说你想创建原始list的平方list:

squares = []
for x in range(10):
... squares.append(x**2)
...
squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

注意到以上代码会创建一个变量x,它在循环后仍就存在。为了消除这个副作用:

squares = list(map(lambda x: x**2, range(10)))

等同与:

squares = [x**2 for x in range(10)]

这更简洁易懂。 列表解析式在for前后再添加个for或者if。新生成的list会由最外围中括号的for if来计算。举个例子,以下代码混合了2个list中互相不等的元素到新的list。

[(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

等于

combs = []
for x in [1,2,3]:
... for y in [3,1,4]:
... if x != y:
... combs.append((x, y))
...
combs
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

如果[]中的表达式是tuple,像前面例子 (x,y),它必须是在括号内。

vec = [-4, -2, 0, 2, 4]
# create a new list with the values doubled
[x*2 for x in vec]
[-8, -4, 0, 4, 8]
# filter the list to exclude negative numbers
[x for x in vec if x >= 0]
[0, 2, 4]
# apply a function to all the elements
[abs(x) for x in vec]
[4, 2, 0, 2, 4]
# call a method on each element
freshfruit = [' banana', ' loganberry ', 'passion fruit ']
[weapon.strip() for weapon in freshfruit]
['banana', 'loganberry', 'passion fruit']
# create a list of 2-tuples like (number, square)
[(x, x**2) for x in range(6)]
[(0, 0), (1, 1), (2, 4), (3, 9), (4, 16), (5, 25)]
# the tuple must be parenthesized, otherwise an error is raised
[x, x**2 for x in range(6)]
File "", line 1, in
[x, x**2 for x in range(6)]
^
SyntaxError: invalid syntax
# flatten a list using a listcomp with two 'for'
vec = [[1,2,3], [4,5,6], [7,8,9]]
[num for elem in vec for num in elem]
[1, 2, 3, 4, 5, 6, 7, 8, 9]

list列表表达式中科院包含更复杂的表达和运算:

from math import pi
[str(round(pi, i)) for i in range(1, 6)]
['3.1', '3.14', '3.142', '3.1416', '3.14159']

5.1.4 列表解析式的细节

最初的列表解析式中科院包含任何二元运算符,包括另一个列表解析式。下面这个list是一个3*4的矩阵:

matrix = [
... [1, 2, 3, 4],
... [5, 6, 7, 8],
... [9, 10, 11, 12],
... ]

以下代码将上面的3*4矩阵进行转置:

[[row[i] for row in matrix] for i in range(4)]
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

就像我们在上一节看到的,内置的列表解析式其实就是for中的上下文操作,上面的例子等于:

transposed = []
for i in range(4):
... transposed.append([row[i] for row in matrix])
...
transposed
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

同样的,也等于:

transposed = []
for i in range(4):
... # the following 3 lines implement the nested listcomp
... transposed_row = []
... for row in matrix:
... transposed_row.append(row[i])
... transposed.append(transposed_row)
...
transposed
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

在真实代码场景中,你更应该使用Python的内置方法进行以上复杂的操作。zip()起相同的作用:

list(zip(*matrix))
[(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)]

看 Unpacking Argument Lists 这一节能得到上面代码的更多细节。

5.2 删除语句

根据次序移除list中的元素。与pop()不同,pop()返回弹出的元素,它不返回。del还可以切片的删除list元素,或者清空整个list,早先我们有个这样的例子。

a = [-1, 1, 66.25, 333, 333, 1234.5]
del a[0]
a
[1, 66.25, 333, 333, 1234.5]
del a[2:4]
a
[1, 66.25, 1234.5]
del a[:]
a
[]

del 可以删除完整的变量 del a 删除变量a之后我们再引用该变量会报错,我们之后还会探索del的其他用法。

5.3 Tuple 和 Sequence

我们注意到list和字符串有许多共同点,比如说索引和切片操作。以下是2个序列数据类型的例子(具体请看Sequence Types — list, tuple, range)。由于Python一直在进化,也许会有更多的序列数据类型加入。下面是另外一种标准库里的序列数据类型:tuple.一个tuple包含了一系列被逗号隔开的数据,比如说:

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))
# Tuples are immutable:
... t[0] = 88888
Traceback (most recent call last):
File "", line 1, in
TypeError: 'tuple' object does not support item assignment
# but they can contain mutable objects:
... v = ([1, 2, 3], [3, 2, 1])
v
([1, 2, 3], [3, 2, 1])

输出tuple总是被括号包围,它里面的tuple有木有括号都能被解释正确;即使括号是必须的,它们的输入tuple也不一定有括号包围。不可以对tuple中的单个元素复制,但是,tuple中的元素是可变对象时,可以被改变,比如说list。 即使tuple和list看着蛮像,但它们通常在不同的情景不同的目的下使用。Tuple是不可变的,通常是包含了各种各样的序列元素,这些元素可以通过解包(后面会提到)或者指定位置来触达。List是可变的,元素通常是同一类的,我们可以通过迭代器遍历整个list。 必须提到的是,构造tuple的时候。空tuple由一对括号初始化,1个元素的tuple初始化应当由1个值和逗号组成。看着丑但是有效哦。

empty = ()
singleton = 'hello', # <-- note trailing comma
len(empty)
0
len(singleton)
1
singleton
('hello',)

t = 12345, 54321, 'hello!'是tuple解包的例子,12345,54321,’hello’都是tuple的元素。反过来的操作如下: x, y, z = t 以上序列解包操作适用于任何等式右边的序列。序列解包操作要求等式左边的变量个数等同于右边序列的原色个数。当然多元赋值也是可以的,只是右边序列解包操作出来的元素混进tuple输出。

5.4 集合(Sets)

Python引入Set这种无序的,无重复元素的数据结构。我们通常用它来消除重复元素和进行隶属测试。Set也支持一些数学操作 ,交,并,差,对称差。 用中括号或set()来新建sets,需要注意的是,新建空set必须使用set()而不是{},后者是新建dict,我们下一章再讨论dict。 下面是set的简要使用:

basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'}
print(basket) # show that duplicates have been removed
{'orange', 'banana', 'pear', 'apple'}
'orange' in basket # fast membership testing
True
'crabgrass' in basket
False

# Demonstrate set operations on unique letters from two words
...
a = set('abracadabra')
b = set('alacazam')
a # unique letters in a
{'a', 'r', 'b', 'c', 'd'}
a - b # letters in a but not in b
{'r', 'd', 'b'}
a | b # letters in a or b or both
{'a', 'c', 'r', 'd', 'b', 'm', 'z', 'l'}
a & b # letters in both a and b
{'a', 'c'}
a ^ b # letters in a or b but not both
{'r', 'd', 'b', 'm', 'z', 'l'}

与list列表解析式类似,set也支持这样操作:

a = {x for x in 'abracadabra' if x not in 'abc'}
a
{'r', 'd'}

5.5 字典(Dictionaries)

另一个很有用的Python内置数据结构是字典。Dictionaries有时在其他计算机语言里,作为对应储存,对应序列。不比被顺序索引的序列数据类型,Dictionaries是不可变的结构,它被key索引,字符串,数字经常被用作key。Tuple也可以被用作key,只要它仅由数字,字符串或者tuple组成,如果该Tuple直接或间接包含可变对象,那它就不能被用作dictionary的key了。也不能把list用作dictionary的key,因为list不需要额外空间就能被指定索引修改,切片或appen(),enxtend()这样的方法。 对dictionary最好的理解是一个无序的键值对,key在这个dictionary是独一无二的。一对中括号可以创建一个空dictionary。用逗号分隔,按照key:value这样的格式,由{}包围,可以初始化一个dictionary,这也是dictionary输出的格式。 dictionary的主要用法是储存键值对以及通过key拿到value。通过del也能删除一个键值对。如果1个key已经使用,再使用它的值会被更新。用一个不存在的键来取值会报错。 list(d.keys())可以以list的形式随机随机顺序获取dictionary的键,当然可以使用sorted(d.keys()获取顺序键list。想知道一个键是否存在于dictionary,使用in。 这里有些dictionary使用的小例子:


python
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}
list(tel.keys())
['irv', 'guido', 'jack']
sorted(tel.keys())
['guido', 'irv', 'jack']
'guido' in tel
True
'jack' not in tel
False

dict()可以从键值对序列构建一个dictionary:

dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])
{'sape': 4139, 'jack': 4098, 'guido': 4127}

dictionary解析式可以用键值对来创建dictionary。

{x: x**2 for x in (2, 4, 6)}
{2: 4, 4: 16, 6: 36}

当键是简单字符串时,经常用关键词等式来创建1个dict。

dict(sape=4139, guido=4127, jack=4098)
{'sape': 4139, 'jack': 4098, 'guido': 4127}

5.6 遍历方法

通过items()可以同时遍历dictionary的键值对。

knights = {'gallahad': 'the pure', 'robin': 'the brave'}
for k, v in knights.items():
... 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

要同时遍历2个或者更多的序列数据结构,可以通过zip()

questions = ['name', 'quest', 'favorite color']
answers = ['lancelot', 'the holy grail', 'blue']
for q, a in zip(questions, answers):
... print('What is your {0}? It is {1}.'.format(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.

要反向遍历序列,首先要指定list头,然后把改list作为reversed()的参数执行。

for i in reversed(range(1, 10, 2)):
... print(i)
...
9
7
5
3
1

要遍历一个序列,使用sorted对原始list操作会输出一个新的有序list,但是原始list不变。

basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
for f in sorted(set(basket)):
... print(f)
...
apple
banana
orange
pear

在遍历list的时候有时候想修改它,与其修改不如建立个新的list,这样更简单更方便。

import math
raw_data = [56.2, float('NaN'), 51.7, 55.3, 52.5, float('NaN'), 47.8]
filtered_data = []
for value in raw_data:
... if not math.isnan(value):
... filtered_data.append(value)
...
filtered_data
[56.2, 51.7, 55.3, 52.5, 47.8]

5.7 条件语句

if和while语句中包含的条件语句,可不仅仅只是比较,可以是任何操作符。 in 和not操作是原来确认值是否存在于序列中。is和is not是确认2个实例对象是否相同;它仅可以作用于可变对象例如说list这样。所有的比较操作符都有相同的运算优先级,都低于数字操作符。 比较操作可以连起来。比如说a<b==c是测试a<b且b=c。 比较操作符可以和二元操作符一起使用:and, o,比较操作符的结果可以被not颠倒。and, or, not的运算优先级均低于比较操作符,其中,not是有最高优先级,or是最低的,所以,A and not B or C等同于(A and (not B)) or C。通常括号能展现它们的各自组成。 and, or这俩二元操作符被称为短路操作符,参数由左到右运算,有结果输出。如果A and B是true, 那即使B是flase, A and B and C也不会去计算C的结果。当二元操作符被用作普通值运算而不是二元运算,短路操作的返回值就是最后一个参数。 可以吧比操作运算和其他二元运算的结果赋值给一个变量

string1, string2, string3 = '', 'Trondheim', 'Hammer Dance'
non_null = string1 or string2 or string3
non_null
Trondheim

不像C语言,Python的复制操作不能在操作符运算中出现。C语言开发者也许会对此困惑,但这条禁令有效的避免了C语言中常常出现的一种错误:在表达式中输入=而在此场景中需要的是==

5.8. 序列结构和其他数据结构类型的比较操作

序列结构实例也许会和另外的相同数据结构的实例进行比较操作。比较操作会按照字母顺序:2个序列的第一个元素比较,如果它们不同,那输出比较操作;如果相同,下一对元素进行比较,直到一方元素遍历结束。如果是相同类型的序列结构,字母级比较操作还会递归进行。如果所有的元素都相同,那我们任务这俩序列结构的实例是相同的。如果一个是另外一个的子集,那短的就是小的那一个。字母级比较使用字符串的Unicode编码顺序来比较。下面是例子

(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)

比较2个不同数据类型的实例也是可以的,只要比较的对象都有合适的方法。比如说不同的数字类型比较时,比较对象会转换成它们的数值,如0变成0.0。除此之外,解释器都会报TypeError异常。

原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/62240.html

(0)
上一篇 2021年8月10日
下一篇 2021年8月10日

相关推荐

发表回复

登录后才能评论