python的序列类型及其特点_Fluent Python 笔记——序列类型及其丰富的操作
序列的分類
Python 標(biāo)準(zhǔn)庫(kù)用 C 語(yǔ)言實(shí)現(xiàn)了豐富的序列類型的數(shù)據(jù)結(jié)構(gòu),如:
容器序列(能存放不同類型的數(shù)據(jù)):list、tuple、collections.deque 等
扁平序列(只容納同一類型的數(shù)據(jù)):str、bytes、bytearray、memoryview、array.array
>>> a_list = [1, '2', True, [1, 2, 3], 4.5]
>>> a_str = 'helloworld'
容器序列存放的是對(duì)象的引用,扁平序列存放的是值。即扁平序列是一段連續(xù)的內(nèi)存空間。
>>> a_list = [1, '2', True, [1, 2, 3], 4.5]
>>> embedded_list = a_list[3]
>>> embedded_list
[1, 2, 3]
>>> embedded_list.append(4)
>>> embedded_list
[1, 2, 3, 4]
>>> a_list
[1, '2', True, [1, 2, 3, 4], 4.5]
序列還可以按照是否可變(能夠被修改)進(jìn)行分類:
可變序列:list、bytearray、array.array、collections.deque、memoryview
不可變序列:tuple、str、bytes
>>> a_list = [1, 2, 3]
>>> a_list[0] = 2
>>> a_list
[2, 2, 3]
>>> a_tuple = (1, 2, 3)
>>> a_tuple[0] = 2
Traceback (most recent call last):
File "", line 1, in
TypeError: 'tuple' object does not support item assignment
列表推導(dǎo)
for 循環(huán):
>>> symbols = '!@#$%'
>>> codes = []
>>> for symbol in symbols:
... codes.append(ord(symbol))
...
>>> codes
[33, 64, 35, 36, 37]
列表推導(dǎo):
>>> symbols = '!@#$%'
>>> codes = [ord(symbol) for symbol in symbols]
>>> codes
[33, 64, 35, 36, 37]
通常的原則是,只用列表推導(dǎo)創(chuàng)建新的列表,并盡量保持簡(jiǎn)短。
列表推導(dǎo)(包括集合推導(dǎo)、字典推導(dǎo))、生成器表達(dá)式在 Python3 中有自己的局部作用域。
>>> x = 'ABC'
>>> dummy = [ord(x) for x in x]
>>> x
'ABC'
>>> dummy
[65, 66, 67]
列表推導(dǎo)與 filter/map 的比較:
>>> symbols = '$¢£¥€¤'
>>> beyond_ascii = [ord(s) for s in symbols if ord(s) > 127]
>>> beyond_ascii
[162, 163, 165, 8364, 164]
>>> beyond_ascii = list(filter(lambda c: c > 127, map(ord, symbols)))
>>> beyond_ascii
[162, 163, 165, 8364, 164]
作為記錄的元組
元組其實(shí)是一種數(shù)據(jù)記錄(Record),其中的每個(gè)元素都對(duì)應(yīng)記錄中一個(gè)字段的數(shù)據(jù),字段在元組中的位置則可以用來區(qū)分其含義。
>>> lax_coordinates = (33.9425, -118.408056)
>>> city, year, pop, area = ('Tokyo', 2003, 32450, 8014)
>>> traveler_ids = [('USA', '31195855'), ('BRA', 'CE342567'), ('ESP', 'XDA205856')]
>>> for country, _ in traveler_ids:
... print(country)
...
USA
BRA
ESP
元組拆包
元組拆包可以應(yīng)用到任何可迭代對(duì)象上,唯一的要求即可迭代對(duì)象中的元素?cái)?shù)量與接收這些元素的空檔數(shù)一致(除非用 * 忽略多余的元素)。
元組拆包(平行賦值):
>>> lax_coordinates = (33.9425, -118.408056)
>>> latitude, longitude = lax_coordinates
>>> latitude
33.9425
>>> longitude
-118.408056
不使用中間變量交換兩個(gè)變量的值:
>>> a = 1
>>> b = 2
>>> a, b = b, a
>>> a
2
>>> b
1
使用 * 運(yùn)算符把一個(gè)可迭代對(duì)象拆開作為函數(shù)的參數(shù):
>>> divmod(20, 8)
(2, 4)
>>> t = (20, 8)
>>> divmod(*t)
(2, 4)
元組拆包可以方便一個(gè)函數(shù)以元組的方式返回多個(gè)值,調(diào)用函數(shù)的代碼就可以輕松地(有選擇地)接受這些值。
>>> import os
>>> _, filename = os.path.split('/home/luciano/.ssh/idrsa.pub')
>>> filename
'idrsa.pub'
用 * 處理多余的元素:
>>> a, b, *rest = range(5)
>>> a, b, rest
(0, 1, [2, 3, 4])
>>> a, b, *rest = range(3)
>>> a, b, rest
(0, 1, [2])
>>> a, b, *rest = range(2)
>>> a, b, rest
(0, 1, [])
>>> a, *body, c, d = range(5)
>>> a, body, c, d
(0, [1, 2], 3, 4)
具名元組
collections.namedtuple 可以用來創(chuàng)建一個(gè)帶字段名的元組和一個(gè)有名字的類,便于對(duì)程序進(jìn)行調(diào)試。其類實(shí)例消耗的內(nèi)存與元組是一樣的,跟普通的對(duì)象實(shí)例相比更小一些(不用 __dict__ 存放實(shí)例的屬性)。
>>> from collections import namedtuple
>>> City = namedtuple('City', 'name country population coordinates')
>>> tokyo = City('Tokyo', 'JP', 36.933, (35.689722, 139.691667))
>>> tokyo
City(name='Tokyo', country='JP', population=36.933, coordinates=(35.689722, 139.691667))
>>> tokyo.population
36.933
>>> tokyo.coordinates
(35.689722, 139.691667)
>>> tokyo[1]
'JP'
創(chuàng)建具名元組需要傳入兩個(gè)參數(shù),第一個(gè)是類名,第二個(gè)是類的各個(gè)字段的名稱。后者可以是多個(gè)字符串組成的可迭代對(duì)象或由空格分隔開的字段名組成的字符串。
可以通過字段名或位置獲取某個(gè)字段的信息。
具名元組的 _fields 屬性包含由這個(gè)類中所有字段名稱組成的元組;_asdict() 方法可以把具名元組以 collections.OrderedDict 的形式返回。
切片
關(guān)于切片和區(qū)間忽略最后一個(gè)元素
在切片和區(qū)間操作里不包含最后一個(gè)元素是 Python 的風(fēng)格,同時(shí)也符合 C 和其他以 0 為起始下標(biāo)的語(yǔ)言的習(xí)慣。
部分原因如下:
當(dāng)只有最后一個(gè)位置信息時(shí),可以快速看出區(qū)間里包含多少個(gè)元素:range(3) 和 my_list[:3] 都返回 3 個(gè)元素
起止位置都可見時(shí),可以快速算出區(qū)間的長(zhǎng)度(stop - start),如切片 my_list[3:6] 即包含 6 - 3 = 3 個(gè)元素
可以利用任意一個(gè)下標(biāo)把序列分割成不重疊的兩部分(my_list[:x] 和 my_list[x:])
step
可以用 s[a:b:c] 的形式對(duì) s 在 a 和 b 之間以 c 為間隔取值。c 值還可以為負(fù),表示反向取值。
>>> s = 'bicycle'
>>> s[::3]
'bye'
>>> s[::-1]
'elcycib'
>>> s[::-2]
'eccb'
對(duì) seq[start:stop:step] 求值時(shí),Python 會(huì)調(diào)用 seq.__getitem__(slice(start, stop, step))。
對(duì)切片賦值
如果把切片放在賦值語(yǔ)句左邊,或把它作為 del 操作的對(duì)象,則可以對(duì)切片所屬的序列進(jìn)行拼接、切除或就地修改等操作。
>>> l = list(range(10))
>>> l
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> l[2:5] = [20, 30]
>>> l
[0, 1, 20, 30, 5, 6, 7, 8, 9]
>>> del l[5:7]
>>> l
[0, 1, 20, 30, 5, 8, 9]
>>> l[3::2] = [11, 22]
>>> l
[0, 1, 20, 11, 5, 22, 9]
>>> l[2:5] = [100]
>>> l
[0, 1, 100, 22, 9]
需要注意的是,在對(duì)切片進(jìn)行賦值操作時(shí),賦值語(yǔ)句的右側(cè)必須是個(gè)可迭代對(duì)象。
對(duì)序列使用 + 和 *
Python 程序員一般默認(rèn)序列都會(huì)支持 + 和 * 的拼接操作。在拼接過程中,兩個(gè)被操作的序列不會(huì)發(fā)生任何改動(dòng),Python 會(huì)創(chuàng)建一個(gè)新的包含拼接結(jié)果的序列。
>>> l = [1, 2, 3]
>>> l * 5
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
>>> 5 * 'abcd'
'abcdabcdabcdabcdabcd'
如果 a * n 語(yǔ)句中序列 a 里的元素是對(duì)其他可變對(duì)象的引用的話,這個(gè)式子的結(jié)果可能會(huì)出乎意料。比如用 my_list = [[]] * 3 來初始化一個(gè)有列表組成的列表,實(shí)際上得到的列表里包含的三個(gè)元素是三個(gè)引用,且這三個(gè)引用都指向同一列表。
>>> weird_board = [['-'] * 3] * 3
>>> weird_board
[['-', '-', '-'], ['-', '-', '-'], ['-', '-', '-']]
>>> weird_board[1][2] = 'O'
>>> weird_board
[['-', '-', 'O'], ['-', '-', 'O'], ['-', '-', 'O']]
其錯(cuò)誤的本質(zhì)等同于如下代碼:
>>> row = ['-'] * 3
>>> board = []
>>> for i in range(3):
... board.append(row)
...
>>> board[1][2] = 'O'
>>> board
[['-', '-', 'O'], ['-', '-', 'O'], ['-', '-', 'O']]
即追加同一個(gè)行對(duì)象(row)到游戲幣(board)
正確的做法代碼如下:
>>> board = [['-'] * 3 for i in range(3)]
>>> board
[['-', '-', '-'], ['-', '-', '-'], ['-', '-', '-']]
>>> board[1][2] = 'O'
>>> board
[['-', '-', '-'], ['-', '-', 'O'], ['-', '-', '-']]
等同于如下代碼:
>>> board = []
>>> for i in range(3):
... row = ['-'] * 3
... board.append(row)
...
>>> board
[['-', '-', '-'], ['-', '-', '-'], ['-', '-', '-']]
>>> board[1][2] = 'O'
>>> board
[['-', '-', '-'], ['-', '-', 'O'], ['-', '-', '-']]
即每次迭代中都新建了一個(gè)列表,作為新的一行(row)追加到游戲板子(board)
序列的增量賦值
增量賦值運(yùn)算符 += 和 *= 的行為取決于第一個(gè)操作對(duì)象。
+= 調(diào)用的特殊方法是 __iadd__(自增)。如果某個(gè)類沒有實(shí)現(xiàn)該方法,Python 會(huì)退一步調(diào)用 __add__。
如 a += b 就會(huì)調(diào)用 a 中實(shí)現(xiàn)的 __iadd__ 方法,同時(shí)對(duì)于可變序列(如 list、bytearray、array.array),該方法的行為類似于 a.extend(b),在 a 上就地改動(dòng)。
如 a 沒有實(shí)現(xiàn) __iadd__,a += b 的效果就類似于 a = a + b,計(jì)算 a + b 得到一個(gè)新的對(duì)象,再把這個(gè)對(duì)象賦值給 a。
*= 對(duì)應(yīng)的是 __imul__。
>>> l = [1, 2, 3]
>>> id(l)
2888988078920
>>> l *= 2
>>> l
[1, 2, 3, 1, 2, 3]
>>> id(l)
2888988078920
>>> t = (1, 2, 3)
>>> id(t)
2888988799688
>>> t *= 2
>>> id(t)
2888988107592
作為可變對(duì)象的列表運(yùn)用增量乘法后,ID 沒變;而作為不可變對(duì)象的元組運(yùn)用增量乘法后,新的元組被創(chuàng)建。
因此對(duì)于不可變序列做重復(fù)拼接操作效率會(huì)很低,每次都會(huì)有一個(gè)新對(duì)象。但字符串除外,由于對(duì)字符串做 += 等操作太普遍,CPython 專門做了優(yōu)化。在為字符串初始化內(nèi)存時(shí),程序會(huì)預(yù)留額外的可擴(kuò)展空間。
list.sort 與 sorted
list.sort 方法會(huì)就地排序列表,即在原列表的基礎(chǔ)上完成排序,不會(huì)再另外復(fù)制一份。也因此其返回值為 None。
內(nèi)置的 sorted 函數(shù)則會(huì)新建一個(gè)列表作為返回值。它可以接收任何形式的可迭代對(duì)象(包含不可變序列和生成器),最后返回的始終是排序好的列表。
>>> fruits = ['grape', 'raspberry', 'apple', 'banana']
>>> sorted(fruits)
['apple', 'banana', 'grape', 'raspberry']
>>> fruits
['grape', 'raspberry', 'apple', 'banana']
>>> sorted(fruits, key=len)
['grape', 'apple', 'banana', 'raspberry']
>>> fruits
['grape', 'raspberry', 'apple', 'banana']
>>> fruits.sort()
>>> fruits
['apple', 'banana', 'grape', 'raspberry']
參考資料
總結(jié)
以上是生活随笔為你收集整理的python的序列类型及其特点_Fluent Python 笔记——序列类型及其丰富的操作的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql行列转置-图文详解
- 下一篇: python怎么写方程条件,条件方程曲线