1. 程式人生 > 其它 >Python元組(Tuple)

Python元組(Tuple)

元組的基本操作

建立元組

Python中,元組(tuple)用一對小括號()表示,元組內的各元素以逗號分隔。

t = ()
print(type(t))  # <type 'tuple'>
t1 = ('name', )
print(t1)  # ('name',)
print(type(t1))  # <type 'tuple'>

元組中特別注意逗號的使用,一不小心建立的元組就會成為字串。

t = ('name')
print(t, type(t))  # ('name', <type 'str'>)
t1 = ('name', )
print(t1, type(t1))  # (('name',), <type 'tuple'>)

索引和切片

元組中索引和切片的用法跟列表和字串類似,或者取範圍內的值,或者可以指定在一段範圍內,每幾個取一個:

t = ('a', 'b', 'c', 'd', 'e')
# 按照索引取值
print(t[1])  # b
print(t[-1]) # e
​
# 取兩者之間(範圍)的值
print(t[0:4])  # ('a', 'b', 'c', 'd')
print(t[0:5:2])  # ('a', 'c', 'e')
​
# 反轉元組,返回反轉後的新的元組,原本的元組不變
print(t[::-1])  # ('e', 'd', 'c', 'b', 'a')
​
# for迴圈取值
for i in t:
    print(i)
'''
a
b
c
d
e
'''

但與列表不同的是,元組身為不可變型別,無法原地修改其中的值。

t = ('a', 'b', 'c', 'd', 'e')
t[2] = 'w'
​
'''
TypeError: 'tuple' object does not support item assignment
'''

拼接:+

雖然元組無法原地修改其中的元素,但是,通過+號可以把兩個元組合併為一個新的元組。

t1 = ('a', 'b')
t2 = ('c', 'd', 'e')
t3 = t1 + t2
print(t3)  # ('a', 'b', 'c', 'd', 'e')

元組的重複: *

簡單來說,正如字串的重複一樣,當對元組使用

*時,複製指定次數後合併為一個新的元組。

t1 = ('a', 'b')
t2 = t1 * 2
print(t2)  # ('a', 'b', 'a', 'b')

成員資格測試: innot in

與字串、列表的成員資格測試在元組中同樣適用:

t1 = ('a', 'b', 'abc')
print('a' in t1)  # True
print('b' not in t1)  # False

需要注意的是,成員資格判斷,只是會判斷某個元素是否存是元組的一級元素,比如上例中的a是元組的一級元素。而c不是元組的一級元素。在元組中,c是元組一級元素字串abc的子串。所以判斷結果為False。

序列(元組)型別的打包與解包

t = 1, 2, 3
x, y, z = t
print(x, y, z)  # 1 2 3

上例第1行,將1、2、3打包賦值給變數t,相當於將3個蘋果打包到一個盒子內。第2行,從盒子t中將3個蘋果取出來,分別交給x、y、z,我們稱為解包。解包這裡需要注意的是,盒子裡有幾個蘋果,必須有幾個對應的變數接收。多了不行,少了也不行。

平行賦值

>>> x, y = 1, 2  
>>> x,y  
(1, 2)  
>>> x  
1  
>>> type(x)  
<class 'int'>  
>>> a = x,y  
>>> a  
(1, 2)  
>>> type(a)  
<class 'tuple'>  

如上例第1行所示,平行賦值就是等號右邊的1,2分別賦值給等號左邊的x,y。第2行就是打包了(只是打包,並沒有賦值給某個變數),並且打包後的結果是元組型別。而在第8行將x,y打包並賦值給變數a。此時a就是打包後的元組了。

通過列印斐波那契序列來練習平行賦值:

x, y = 0, 1
while x < 8:
    x, y = y, x + y
    print(x)
'''
1
1
2
3
5
8
'''

首先定義x,y兩個變數並賦值。在每次迴圈中,x和y值都會重新賦值。

刪除元組

注意,這裡說的刪除僅是刪除整個元組,而不是刪除元組中某個元素。

>>> t = (1, 2, 3)  
>>> del t[1]  
Traceback (most recent call last):  
  File "<stdin>", line 1, in <module>  
TypeError: 'tuple' object doesn't support item deletion  
>>> del t  
>>> t  
Traceback (most recent call last):  
  File "<stdin>", line 1, in <module>  
NameError: name 't' is not defined  

上例第2行,通過刪除元組內的元素導致報錯,又一次的證明元組為不可變型別。但我們可以刪除整個元組(第6行)。

來總結,元組中常用的操作符:

操作符(表示式) 描述 重要程度
+ 合併 **
* 重複 **
in 成員資格 **
for i in (1, 2, 3):print(i) 迭代 *
t[2] 索引取值 *
t[start:stop:step] 切片(擷取) *

另外,還有幾個內建的函式可以應用於元組:

方法 描述 重要程度
max(tuple) 返回元組內最大的元素 **
min(tuple) 返回元組內最小的元素 **
tuple(seq) 將序列轉換為元組 *
len(tuple) 返回元組長度 *

 

 

元組的巢狀

與列表一樣,元組也能巢狀儲存資料:

t = (1, (2, 3), [4, [5, 'c']], {'a': 'b'}, {8, 7})
for item in t:
    print(item)
'''
1
(2, 3)
[4, [5, 'c']]
{'a': 'b'}
{8, 7}
'''

元組內可以儲存的資料型別相當豐富,也可以發現,巢狀元素也會當成一個整體稱為元組的子元素。但是我們說元組是不可更改的,但我們發現其中是由列表的,這是怎麼回事?

t = (1, (2, 3), [4, [5, 'c']], {'a': 'b'}, {8, 7})
t[0] = 'x'  # TypeError: 'tuple' object does not support item assignment

通過上例,可以發現,元組內的普通元素不允許修改,但是列表是可變,我們能否把列表替換為別的元素呢?

t = (1, (2, 3), [4, [5, 'c']], {'a': 'b'}, {8, 7})
print(t[2])  # [4, [5, 'c']]
t[2] = 'w'  # TypeError: 'tuple' object does not support item assignment

通過上例也可以發現,如果列表被當成了元組的普通元素,那麼它也是不可以修改的,遵循元組不可變特性。但是我們如果試圖修改列表中的子元素呢?

t = (1, (2, 3), [4, [5, 'c']], {'a': 'b'}, {8, 7})
t[2][0] = 'w'
print(t)  # (1, (2, 3), ['w', [5, 'c']], {'a': 'b'}, {8, 7})

上例,可以發現,t[2][0]指的是列表中的第一個元素,我們在第2行修改它,然後發現是可以修改成功的。這是為什麼呢?元組內的普通元素不允許修改,巢狀的子元素是否能夠修改取決於這個子元素本身屬於什麼資料型別,如這個子元素是列表,那就可以修改,如果是元組,就可不以修改。

那麼,瞭解完元組,你可能更有疑問了,除了不可變之外,元組跟列表沒啥區別麼?我們通過與列表的對比,來證明存在即是真理!

需要注意的是,元組並沒有增、刪功能。那麼元組如此設計,以放棄增、刪為代價換來了什麼呢?

效能!是的,換來了效能!不信請看,以下演示程式碼由IPython(Python的另一個發行版本)完成。

 

 

list VS tuple

list VS tuple:建立(生成)速度

我們通過建立同樣大小的list和tuple,來觀察有什麼變化:

In [1]: % timeit [1, 2, 3, 4, 5]
139 ns ± 2.34 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
​
In [2]: % timeit (1, 2, 3, 4, 5)
17.3 ns ± 0.161 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)

上例的意思是說我們建立一個長度為5的列表,大概執行了7次大迴圈,每次大迴圈中進行10000000次小迴圈。這個7次大迴圈,每次耗時(平均值)139 ns,每次(標準)偏差2.34 ns。而建立同樣長度的元組,每次僅耗時17.3 ns,對比建立列表的耗時139 ns,可以看到建立元組的速度快很多。

list VS tuple:遍歷速度

In [13]: from numpy.random import rand
​
In [14]: values = rand(5, 2)
​
In [15]: values
Out[15]:
array([[0.58715281, 0.80168228],
       [0.18092562, 0.38003109],
       [0.7041874 , 0.36891089],
       [0.49066082, 0.4369031 ],
       [0.66990039, 0.61642406]])
​
In [16]: l = [list(row) for row in values]
​
In [17]: l
Out[17]:
[[0.5871528101592326, 0.801682278879223],
 [0.1809256206032368, 0.3800310899685857],
 [0.704187400986516, 0.36891089280681766],
 [0.4906608153801344, 0.43690309811990113],
 [0.6699003858521562, 0.6164240631243966]]
​
In [18]: t = tuple(tuple(row) for row in values)
​
In [19]: t
Out[19]:
((0.5871528101592326, 0.801682278879223),
 (0.1809256206032368, 0.3800310899685857),
 (0.704187400986516, 0.36891089280681766),
 (0.4906608153801344, 0.43690309811990113),
 (0.6699003858521562, 0.6164240631243966))
In [20]: % timeit for row in l: list(row)
4.73 µs ± 78.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
​
In [21]: % timeit for row in t: tuple(row)
823 ns ± 26.4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

我們迴圈遍歷元組和列表,可以看到遍歷列表耗時4.73 µs,而元組這邊耗時823 ns

要知道1微秒(μs)=1000納秒(ns),元組的遍歷效能相比列表也是快很多了。 擴充套件:

1秒 = 1000毫秒
1毫秒 = 1000微秒
1微秒 = 1000納秒
1納秒 = 1000皮秒
1s = 1000ms
1ms = 1000μs
1μs = 1000ns
1ns = 1000ps

list VS tuple:儲存開銷

再來看他們的儲存開銷:

from sys import getsizeof
l = [1, 2, 3, 4, 5]
t = (1, 2, 3, 4, 5)
print(getsizeof(l))  # 56
print(getsizeof(t))  # 48

可以看到,元組在儲存空間上面也佔優勢。

list VS tuple:雜湊比較

l = [1, 2, 3, 4, 5]
t = (1, 2, 3, 4, 5)
print(hash(t))  # -1883319094
print(hash(l))  # TypeError: unhashable type: 'list'

簡單來說,Hash,一般翻譯做“雜湊”,也有直接音譯為“雜湊”的,就是把任意長度的輸入(又叫做預對映, pre-image),通過雜湊演算法,變換成固定長度的輸出,該輸出就是雜湊值。這種轉換是一種壓縮對映,也就是,雜湊值的空間通常遠小於輸入的空間,不同的輸入可能會雜湊成相同的輸出,所以不可能從雜湊值來確定唯一的輸入值。簡單的說就是一種將任意長度的訊息壓縮到某一固定長度的訊息摘要的函式。

Python中可雜湊(hashable)型別:字串、元組、物件。可雜湊型別就是我們常說的不可變型別,優點是效能經過優化,多執行緒安全,不需要鎖,不擔心被惡意篡改或者不小心修改了。

不可雜湊型別:字典、列表、集合。相對於可雜湊型別,使用起來相對靈活。

 


Reference:

圖片來源:https://realpython.com/