python的序列类型及其特点_Fluent Python 笔记——序列类型及其丰富的操作
序列的分類
Python 標準庫用 C 語言實現了豐富的序列類型的數據結構,如:
容器序列(能存放不同類型的數據):list、tuple、collections.deque 等
扁平序列(只容納同一類型的數據):str、bytes、bytearray、memoryview、array.array
>>> a_list = [1, '2', True, [1, 2, 3], 4.5]
>>> a_str = 'helloworld'
容器序列存放的是對象的引用,扁平序列存放的是值。即扁平序列是一段連續的內存空間。
>>> 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]
序列還可以按照是否可變(能夠被修改)進行分類:
可變序列: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
列表推導
for 循環:
>>> symbols = '!@#$%'
>>> codes = []
>>> for symbol in symbols:
... codes.append(ord(symbol))
...
>>> codes
[33, 64, 35, 36, 37]
列表推導:
>>> symbols = '!@#$%'
>>> codes = [ord(symbol) for symbol in symbols]
>>> codes
[33, 64, 35, 36, 37]
通常的原則是,只用列表推導創建新的列表,并盡量保持簡短。
列表推導(包括集合推導、字典推導)、生成器表達式在 Python3 中有自己的局部作用域。
>>> x = 'ABC'
>>> dummy = [ord(x) for x in x]
>>> x
'ABC'
>>> dummy
[65, 66, 67]
列表推導與 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]
作為記錄的元組
元組其實是一種數據記錄(Record),其中的每個元素都對應記錄中一個字段的數據,字段在元組中的位置則可以用來區分其含義。
>>> 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
元組拆包
元組拆包可以應用到任何可迭代對象上,唯一的要求即可迭代對象中的元素數量與接收這些元素的空檔數一致(除非用 * 忽略多余的元素)。
元組拆包(平行賦值):
>>> lax_coordinates = (33.9425, -118.408056)
>>> latitude, longitude = lax_coordinates
>>> latitude
33.9425
>>> longitude
-118.408056
不使用中間變量交換兩個變量的值:
>>> a = 1
>>> b = 2
>>> a, b = b, a
>>> a
2
>>> b
1
使用 * 運算符把一個可迭代對象拆開作為函數的參數:
>>> divmod(20, 8)
(2, 4)
>>> t = (20, 8)
>>> divmod(*t)
(2, 4)
元組拆包可以方便一個函數以元組的方式返回多個值,調用函數的代碼就可以輕松地(有選擇地)接受這些值。
>>> 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 可以用來創建一個帶字段名的元組和一個有名字的類,便于對程序進行調試。其類實例消耗的內存與元組是一樣的,跟普通的對象實例相比更小一些(不用 __dict__ 存放實例的屬性)。
>>> 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'
創建具名元組需要傳入兩個參數,第一個是類名,第二個是類的各個字段的名稱。后者可以是多個字符串組成的可迭代對象或由空格分隔開的字段名組成的字符串。
可以通過字段名或位置獲取某個字段的信息。
具名元組的 _fields 屬性包含由這個類中所有字段名稱組成的元組;_asdict() 方法可以把具名元組以 collections.OrderedDict 的形式返回。
切片
關于切片和區間忽略最后一個元素
在切片和區間操作里不包含最后一個元素是 Python 的風格,同時也符合 C 和其他以 0 為起始下標的語言的習慣。
部分原因如下:
當只有最后一個位置信息時,可以快速看出區間里包含多少個元素:range(3) 和 my_list[:3] 都返回 3 個元素
起止位置都可見時,可以快速算出區間的長度(stop - start),如切片 my_list[3:6] 即包含 6 - 3 = 3 個元素
可以利用任意一個下標把序列分割成不重疊的兩部分(my_list[:x] 和 my_list[x:])
step
可以用 s[a:b:c] 的形式對 s 在 a 和 b 之間以 c 為間隔取值。c 值還可以為負,表示反向取值。
>>> s = 'bicycle'
>>> s[::3]
'bye'
>>> s[::-1]
'elcycib'
>>> s[::-2]
'eccb'
對 seq[start:stop:step] 求值時,Python 會調用 seq.__getitem__(slice(start, stop, step))。
對切片賦值
如果把切片放在賦值語句左邊,或把它作為 del 操作的對象,則可以對切片所屬的序列進行拼接、切除或就地修改等操作。
>>> 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]
需要注意的是,在對切片進行賦值操作時,賦值語句的右側必須是個可迭代對象。
對序列使用 + 和 *
Python 程序員一般默認序列都會支持 + 和 * 的拼接操作。在拼接過程中,兩個被操作的序列不會發生任何改動,Python 會創建一個新的包含拼接結果的序列。
>>> 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 語句中序列 a 里的元素是對其他可變對象的引用的話,這個式子的結果可能會出乎意料。比如用 my_list = [[]] * 3 來初始化一個有列表組成的列表,實際上得到的列表里包含的三個元素是三個引用,且這三個引用都指向同一列表。
>>> weird_board = [['-'] * 3] * 3
>>> weird_board
[['-', '-', '-'], ['-', '-', '-'], ['-', '-', '-']]
>>> weird_board[1][2] = 'O'
>>> weird_board
[['-', '-', 'O'], ['-', '-', 'O'], ['-', '-', 'O']]
其錯誤的本質等同于如下代碼:
>>> row = ['-'] * 3
>>> board = []
>>> for i in range(3):
... board.append(row)
...
>>> board[1][2] = 'O'
>>> board
[['-', '-', 'O'], ['-', '-', 'O'], ['-', '-', 'O']]
即追加同一個行對象(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'], ['-', '-', '-']]
即每次迭代中都新建了一個列表,作為新的一行(row)追加到游戲板子(board)
序列的增量賦值
增量賦值運算符 += 和 *= 的行為取決于第一個操作對象。
+= 調用的特殊方法是 __iadd__(自增)。如果某個類沒有實現該方法,Python 會退一步調用 __add__。
如 a += b 就會調用 a 中實現的 __iadd__ 方法,同時對于可變序列(如 list、bytearray、array.array),該方法的行為類似于 a.extend(b),在 a 上就地改動。
如 a 沒有實現 __iadd__,a += b 的效果就類似于 a = a + b,計算 a + b 得到一個新的對象,再把這個對象賦值給 a。
*= 對應的是 __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
作為可變對象的列表運用增量乘法后,ID 沒變;而作為不可變對象的元組運用增量乘法后,新的元組被創建。
因此對于不可變序列做重復拼接操作效率會很低,每次都會有一個新對象。但字符串除外,由于對字符串做 += 等操作太普遍,CPython 專門做了優化。在為字符串初始化內存時,程序會預留額外的可擴展空間。
list.sort 與 sorted
list.sort 方法會就地排序列表,即在原列表的基礎上完成排序,不會再另外復制一份。也因此其返回值為 None。
內置的 sorted 函數則會新建一個列表作為返回值。它可以接收任何形式的可迭代對象(包含不可變序列和生成器),最后返回的始終是排序好的列表。
>>> 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']
參考資料
總結
以上是生活随笔為你收集整理的python的序列类型及其特点_Fluent Python 笔记——序列类型及其丰富的操作的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql行列转置-图文详解
- 下一篇: Hive的查找语法