Sean talks

Attitude is everything

0%

Python 學習筆記 - 內建序列 (list & tuple)

概述

listtuple 都是 Python 中提供的陣列資料結構,當我們要建立陣列時,我們需先配置一塊記憶體區段 (其中的每一個區段都會被當成指向實際資料的整數指標),其中 :

  • list 為動態陣列型態
  • tuple 則是靜態陣列 (內容固定不可變的)

list 的可改變大小及修改特性意味著其比 tuple 需要存去更多的額外記憶體以計算。

list 為例:

1
list1 = [7, 8, 9, 10, 11]  

在記憶體內的操作即是 :

  1. 配置 list1 所需要的記憶體空間
  2. 產生一個空的 list 並存入這些指向元素的 pointer (如 list1 = 0x003)

因此,當我們需要取出 list1 的第 4 個元素時,我們就會透過指標前往該序列,並取出第 4 個貯體位置內容。

串列 (List)

串列的基本操作

1
2
3
4
5
6
7
year = 2021  
company = 'Apple'
product = ['Apple Watch', 'Iphone 12', 'Ipad Pro', 'Ipad Air', 'iMac']
price = [8900, 26900, 30900, 18900, 35900]
appear_on_the_market = True

apple_inf = [year, company, product, price, appear_on_the_market]
1
2
3
4
5
6
7
apple_inf  
>>>
[2021,
'Apple',
['Apple Watch', 'Iphone 12', 'Ipad Pro', 'Ipad Air', 'iMac'],
[8900, 26900, 30900, 18900, 35900],
True]

list.append(x) : 將一個新的項目加到 list 的尾端。

list.extend(iterable)將 iterable(可列舉物件)接到 list 的尾端。

list.insert(ix)將一個項目插入至 list 中給定的位置。第一個引數為插入處前元素的索引值,舉例來說:

  • a.insert(0, x) 會插入為 list 首位
  • a.insert(len(a), x) 插入為末位

list.remove(x)移除列表中第一个值为 x 的元素。

list.pop([i])移除 list 中給定位置的項目,並回傳它。如果沒有指定位置, a.pop() 將會移除 list 中最後的項目並回傳它。

list.clear()刪除 list 中所有項目。這等同於 del a[:] 。

list.index(x[, start[, end]]) 回傳 list 中第一個值等於 x 的項目之索引值(從零開始的索引往後計算)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
_iter =  100000  
start = time.time()

for i in range(_iter):
apple_inf[2].index('Ipad Air', 0)

t1 = time.time() - start
start = time.time()

for i in range(_iter):
apple_inf[2].index('Ipad Air', 2)

t2 = time.time() - start

print('Execution time: ')
print('t1: ', str(t1))
print('t2: ', str(t2))

>>>
Execution time:
t1: 0.021004676818847656
t2: 0.018004655838012695

index 於以設定從索引處開始向後搜尋,因此可以看到 t2 的搜尋速度快於 t1

list.count(x)回傳數值為 x 在 list 中所出現的次數。

list.sort(***, key=Nonereverse=False)將 list 中的項目排序。

list.reverse()將 list 中的項目前後順序反過來。

1
2
3
4
5
6
apple_inf[2].reverse()  
print(apple_inf[2], apple_inf[2][::-1])

>>>
(['iMac', 'Ipad Air', 'Ipad Pro', 'Iphone 12', 'Apple Watch'],
['Apple Watch', 'Iphone 12', 'Ipad Pro', 'Ipad Air', 'iMac'])

step 參數指定為 -1 時 可以將 list 的資料順序顛倒。

list.copy()回傳一個淺複製 (shallow copy) 的 list 。等同於 a[:]。

List Comprehensions

listcomp 可以用簡潔的方法創建 list,提高程式易讀性,可用在 iterable 物件。

1
2
3
4
num = "aeqwFWD"  
count = []
for i in num:
count.append(ord(i))
1
2
num = "aeqwFWD"  
count = [ord(i) for i in num]

listcomp 將目的表示更為明確,容易理解。
程式超過兩行,使用一般 for 迴圈或許較好。

list超額配置

當你對大小為 N 的串列附加 1 個資料時, python 會建立 N + M 個 額外空間應付未來的附加,接著將舊串列的資料複製到新串列中,再將舊串列銷毀。 (圖 3-3 p.74)

Tuple

雖然 tuple 和 list 看起來很類似,但是他們通常用在不同的情況與不同目的。

tuple 是 immutable (不可變的),通常儲存異質的序列元素,並可經由拆解(unpacking) (請參考本節後段)或索引 (indexing) 來存取(或者在使用 namedtuples 的時候藉由屬性 (attribute) 來存取)。 list 是 mutable (可變的),其元素通常是同質的且可藉由迭代整個串列來存取。

用 Tuple 紀錄

序列封裝 (tuple packing)

1
2
3
4
5
6
7
metro_area = [  
('Tokyo', 2020, 42340, 35.677700, 139.818298),
('Taipei', 2020, 32340, 25.038792, 121.521580)
]
metro_area
>>>
('Tokyo', 2020, 42340, 35.6777, 139.818298)

序列拆解 (sequence unpacking)

1
2
3
4
city, year, pop, lat, lon = metro_area[0]  
year, pop, lat, lon
>>>
(2020, 42340, 35.6777, 139.818298)

namedtuple

collection.namedtuple 可以用來製造 tuple 子類別,並加入欄位名稱與類別名稱 (協助除錯)。

1
2
3
4
5
6
7
8
9
10
11
12
from collections import namedtuple  

City = namedtuple('City', ['name', 'year', 'pop', 'lat', 'lon'])
# 提供跌代字串方法,相等於 City = namedtuple('City', 'name year pop lat lon')

tokyo = City('Tokyo', 2020, 42340, 35.677700, 139.818298)
taipei = City('Taipei', 2020, 32340, 25.038792, 121.521580)

tokyo, taipei
>>>
(City(name='Tokyo', year=2020, pop=42340, lat=35.6777, lon=139.818298),
City(name='Taipei', year=2020, pop=32340, lat=25.038792, lon=121.52158))

使用 namedtuple 所用到的記憶體空間與 tuple 完全一樣,使用的記憶體比一般物件少,因為不會儲存到 dict 中的屬性。

1
2
3
metro_area[0].__sizeof__(), tokyo.__sizeof__()  
>>>
(64, 64)

namedtuple 除了繼承 tuple 屬性以外,最常使用的還包括 _fields 類別屬性, _make(iterable) 類別方法, _asdict() 實例方法

_fields

1
2
3
City._fields  
>>>
('name', 'year', 'pop', 'lat', 'lon')

_make(iterable)

實例化一個 namedtuple

1
2
3
4
taichung = ['Taichung', 2020 ,23041 , 24.161453, 120.688604]  
City._make(taichung) # 相等於 City(*taichung)
>>>
City(name='Taichung', year=2020, pop=23041, lat=24.161453, lon=120.688604)

_asdict()

回傳 collections.OrderedDict 不是 dict (3.1 到3.7)

回傳 dict (3.8)

1
2
3
4
5
6
7
8
tokyo._asdict(), type(tokyo._asdict())  
>>>
({'name': 'Tokyo',
'year': 2020,
'pop': 42340,
'lat': 35.6777,
'lon': 139.818298},
dict)

將 Tuple 當成不可變的串列

tuple 支援所有不涉及添加移除的串列方法。

List 與 Tuple 比較

1
2
3
4
5
6
7
taichung = ['Taichung', 2020 ,23041 , 24.161453, 120.688604]  
taichung, City(*taichung)
>>>
(['Taichung', 2020, 23041, 24.161453, 120.688604],
City(name='Taichung', year=2020, pop=23041, lat=24.161453, lon=120.688604),
80,
64)

以上可以看出,在存取相同元素時, List 的存取大小要比 Tuple 來得大。這是因為 List 為動態存取,需要存取指標來指向對應的元素位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
kaohsiung = list()  
print(kaohsiung.__sizeof__())

kaohsiung.append('Kaohsiung')
print(kaohsiung.__sizeof__())

kaohsiung.append(2020)
print(kaohsiung.__sizeof__())

kaohsiung.append(19240)
print(kaohsiung.__sizeof__())

kaohsiung.append(22.623562)
print(kaohsiung.__sizeof__())

kaohsiung.append(120.303034)
print(kaohsiung.__sizeof__())

>>>
40
72
72
72
72
104

因為 List 為可變的,一般來說會保留額外的存儲空間,當佔用空間不足時,再額外分配新空間使用。

接著測試 List 與 Tuple 的效能

1
2
3
4
5
6
7
import timeit  

timeit.timeit("kaohsiung = ['Kaohsiung', 2020, 19240, 22.623562, 120.303034]"), \
timeit.timeit("kaohsiung = ('Kaohsiung', 2020, 19240, 22.623562, 120.303034)")

>>>
(0.0898362999996607, 0.017529799999465467)

泛用的程式碼比專門設計來解決特定問題的程式碼要慢得多,因此之後會再詳細說明 .array 與 numpy 方法之比較。

References