1. 程式人生 > >《Python官方文件》5.資料結構

《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異常。