序列類型
Python 標(biāo)準(zhǔn)庫(kù)用 C 實(shí)現(xiàn)了豐富的序列類型, 列舉如下冀瓦。
容器序列
list、 tuple 和 collections.deque 這些序列能存放不同類型的數(shù)據(jù)调鲸。
扁平序列
str、 bytes挽荡、 bytearray藐石、 memoryview 和 array.array, 這類序列只能容納一種類型定拟。
容器序列存放的是它們所包含的任意類型的對(duì)象的引用于微, 而扁平序列
里存放的是值而不是引用。 換句話說(shuō)青自, 扁平序列其實(shí)是一段連續(xù)的內(nèi)存空間株依。 由此可見(jiàn)扁平序列其實(shí)更加緊湊, 但是它里面只能存放諸如字符延窜、 字節(jié)和數(shù)值這種基礎(chǔ)類型恋腕。序列類型還能按照能否被修改來(lái)分類。
可變序列
list逆瑞、 bytearray荠藤、 array.array、 collections.deque 和
memoryview获高。
不可變序列
tuple哈肖、 str 和 bytes。
圖 2-1 顯示了可變序列( MutableSequence) 和不可變序列
( Sequence) 的差異念秧, 同時(shí)也能看出前者從后者那里繼承了一些方
法牡彻。 雖然內(nèi)置的序列類型并不是直接從 Sequence 和
MutableSequence 這兩個(gè)抽象基類( Abstract Base Class, ABC) 繼承而來(lái)的出爹, 但是了解這些基類可以幫助我們總結(jié)出那些完整的序列類型包含了哪些功能庄吼。
2.2 列表推導(dǎo)和生成器表達(dá)式
說(shuō)白了主要就是 for 循環(huán)
2.2.3 笛卡兒積
>>> colors = ['black', 'white']
>>> sizes = ['S', 'M', 'L']
>>> tshirts = [(color, size) for color in colors for size in sizes] ?
>>> tshirts
[('black', 'S'), ('black', 'M'), ('black', 'L'), ('white', 'S'),
('white', 'M'), ('white', 'L')]
>>> for color in colors: ?
... for size in sizes:
... print((color, size))
...
('black', 'S')
('black', 'M')
('black', 'L')
('white', 'S')
('white', 'M')
('white', 'L')
>>> tshirts = [(color, size) for size in sizes ?
... for color in colors]
>>> tshirts
[('black', 'S'), ('white', 'S'), ('black', 'M'), ('white', 'M'),
('black', 'L'), ('white', 'L')]
2.2.4 生成器表達(dá)式
生成器表達(dá)式的語(yǔ)法跟列表推導(dǎo)差不多, 只不過(guò)把方括號(hào)換成圓括號(hào)而
已
>>> symbols = '$¢£¥€¤'
>>> tuple(ord(symbol) for symbol in symbols) ?
(36, 162, 163, 165, 8364, 164)
>>> import array
>>> array.array('I', (ord(symbol) for symbol in symbols)) ?
array('I', [36, 162, 163, 165, 8364, 164])
2.3 元組不僅僅是不可變的列表
有些 Python 入門(mén)教程把元組稱為“不可變列表”严就, 然而這并沒(méi)有完全概括
元組的特點(diǎn)总寻。 除了用作不可變的列表, 它還可以用于沒(méi)有字段名的記
錄梢为。 鑒于后者常常被忽略渐行, 我們先來(lái)看看元組作為記錄的功用
2.3.1 元組和記錄
元組其實(shí)是對(duì)數(shù)據(jù)的記錄: 元組中的每個(gè)元素都存放了記錄中一個(gè)字段
的數(shù)據(jù), 外加這個(gè)字段的位置铸董。 正是這個(gè)位置信息給數(shù)據(jù)賦予了意義祟印。
In [210]: lax_coordinates = (33.9425, -118.408056)
In [211]: city, year, pop, chg, area = ('Tokyo', 2003, 32450, 0.66, 8014)
In [212]: traveler_ids = [('USA', '31195855'), ('BRA', 'CE342567'),('ESP', 'XDA2
...: 05856')]
In [213]: for passport in sorted(traveler_ids):
...: print('%s/%s' % passport)
...:
BRA/CE342567
ESP/XDA205856
USA/31195855
In [214]: for country, _ in traveler_ids: # 說(shuō)白了就是花式運(yùn)用拆包特性
...: print(country) # 上方 _ 為占位符
...:
USA
BRA
ESP
2.3.2 元組拆包
最好辨認(rèn)的元組拆包形式就是平行賦值, 也就是說(shuō)把一個(gè)可迭代對(duì)象里的元素粟害, 一并賦值到由對(duì)應(yīng)的變量組成的元組中蕴忆。
>>> lax_coordinates = (33.9425, -118.408056)
>>> latitude, longitude = lax_coordinates # ??元組拆包
>>> latitude
33.9425
>>> longitude
-118.408056
另外一個(gè)很優(yōu)雅的寫(xiě)法當(dāng)屬不使用中間變量交換兩個(gè)變量的值:
>>> b, a = a, b
在 Python 中, 函數(shù)用 *args 來(lái)獲取不確定數(shù)量的參數(shù)算是一種經(jīng)典寫(xiě)
法了悲幅。
>>> 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, [])
2.3.3 嵌套元組拆包
In [221]: a,b,c = (1,2,(3,4))
In [222]: a
Out[222]: 1
In [223]: c
Out[223]: (3, 4)
2.3.4 具名元組
collections.namedtuple 是一個(gè)工廠函數(shù)套鹅, 它可以用來(lái)構(gòu)建一個(gè)帶
字段名的元組和一個(gè)有名字的類——這個(gè)帶名字的類對(duì)調(diào)試程序有很大
幫助站蝠。
用 namedtuple 構(gòu)建的類的實(shí)例所消耗的內(nèi)存跟元組是一樣的, 因?yàn)樽侄蚊急淮嬖趯?duì)應(yīng)的類里面卓鹿。 這個(gè)實(shí)例跟普通的對(duì)象實(shí)例比起來(lái)也要小一些菱魔, 因?yàn)?Python 不會(huì)用 __dict__
來(lái)存放這些實(shí)例的屬性。
Card = collections.namedtuple('Card', ['rank', 'suit']) # 構(gòu)建范例
如何用具名元組來(lái)記錄一個(gè)城市的信息:
>>> 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è)具名元組需要兩個(gè)參數(shù)吟孙, 一個(gè)是類名澜倦, 另一個(gè)是類的各個(gè)
字段的名字。 后者可以是由數(shù)個(gè)字符串組成的可迭代對(duì)象杰妓, 或者是由空格分隔開(kāi)的字段名組成的字符串藻治。
? 存放在對(duì)應(yīng)字段里的數(shù)據(jù)要以一串參數(shù)的形式傳入到構(gòu)造函數(shù)中
( 注意, 元組的構(gòu)造函數(shù)卻只接受單一的可迭代對(duì)象)
? 你可以通過(guò)字段名或者位置來(lái)獲取一個(gè)字段的信息稚失。
建立由列表組成的列表
有時(shí)我們會(huì)需要初始化一個(gè)嵌套著幾個(gè)列表的列表栋艳, 譬如一個(gè)列表可能需要用來(lái)存放不同的學(xué)生名單恰聘, 或者是一個(gè)井字游戲板 上的一行方
塊句各。 想要達(dá)成這些目的, 最好的選擇是使用列表推導(dǎo)晴叨。
一個(gè)包含 3 個(gè)列表的列表凿宾, 嵌套的 3 個(gè)列表各自有 3 個(gè)元素來(lái)代表井字游戲的一行方塊 :
>>> board = [['_'] * 3 for i in range(3)] ?
>>> board
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
>>> board[1][2] = 'X' ?
>>> board
[['_', '_', '_'], ['_', '_', 'X'], ['_', '_', '_']]
? 建立一個(gè)包含 3 個(gè)列表的列表, 被包含的 3 個(gè)列表各自有 3 個(gè)元
素兼蕊。 打印出這個(gè)嵌套列表初厚。
? 把第 1 行第 2 列的元素標(biāo)記為 X, 再打印出這個(gè)列表孙技。
2.6 序列的增量賦值
+=
背后的特殊方法是 __iadd__
( 用于“就地加法”) 产禾。 但是如果一個(gè)類
沒(méi)有實(shí)現(xiàn)這個(gè)方法的話, Python 會(huì)退一步調(diào)用 __add__
牵啦。 考慮下面這
個(gè)簡(jiǎn)單的表達(dá)式:
>>> a += b
如果 a 實(shí)現(xiàn)了 __iadd__
方法亚情, 就會(huì)調(diào)用這個(gè)方法。 同時(shí)對(duì)可變序列
( 例如 list哈雏、 bytearray 和 array.array) 來(lái)說(shuō)楞件, a 會(huì)就地改動(dòng), 就
像調(diào)用了 a.extend(b)
一樣裳瘪。 但是如果 a 沒(méi)有實(shí)現(xiàn) __iadd__
的話土浸, a += b
這個(gè)表達(dá)式的效果就變得跟 a = a + b
一樣了: 首先計(jì)算 a + b
, 得到一個(gè)新的對(duì)象彭羹, 然后賦值給 a黄伊。 也就是說(shuō), 在這個(gè)表達(dá)式中派殷,變量名會(huì)不會(huì)被關(guān)聯(lián)到新的對(duì)象毅舆, 完全取決于這個(gè)類型有沒(méi)有實(shí)現(xiàn)
__iadd__
這個(gè)方法西篓。
總體來(lái)講, 可變序列一般都實(shí)現(xiàn)了__iadd__
方法憋活, 因此 +=
是就地加法岂津。 而不可變序列根本就不支持這個(gè)操作, 對(duì)這個(gè)方法的實(shí)現(xiàn)也就無(wú)從談起悦即。
接下來(lái)有個(gè)小例子吮成, 展示的是 *= 在可變和不可變序列上的作用:
>>> l = [1, 2, 3]
>>> id(l)
4311953800 ?
>>> l *= 2
>>> l
[1, 2, 3, 1, 2, 3]
>>> id(l)
4311953800 ?
>>> t = (1, 2, 3)
>>> id(t)
4312681568 ? # 元組不可變,一變內(nèi)存地址就變
>>> t *= 2
>>> id(t) # 對(duì)不可變序列進(jìn)行重復(fù)拼接操作的話辜梳, 效率會(huì)很低
4301348296 ? 運(yùn)用增量乘法后粱甫, 新的元組被創(chuàng)建
??一個(gè)關(guān)于+=的謎題 (有趣)
In [257]: t = (1,2,[30,40])
In [258]: t[2]
Out[258]: [30, 40]
In [259]: type(t)
Out[259]: tuple
In [260]: type(t[2])
Out[260]: list
In [262]: t[2] += [50,60]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-262-b483a1b4fe1d> in <module>()
----> 1 t[2] += [50,60]
TypeError: 'tuple' object does not support item assignment
結(jié)果:
t 變成 (1, 2, [30, 40, 50, 60])
因?yàn)?tuple 不支持對(duì)它的元素賦值, 所以會(huì)拋出 TypeError 異常
教訓(xùn):不要把可變對(duì)象放在元組里面
2.7 list.sort方法和內(nèi)置函數(shù)sorted
list.sort 方法會(huì)就地排序列表作瞄, 也就是說(shuō)不會(huì)把原列表復(fù)制一份茶宵。 這
也是這個(gè)方法的返回值是 None 的原因, 提醒你本方法不會(huì)新建一個(gè)列
表宗挥。 在這種情況下返回 None 其實(shí)是 Python 的一個(gè)慣例.
與 list.sort 相反的是內(nèi)置函數(shù) sorted乌庶, 它會(huì)新建一個(gè)列表作為返回值。 這個(gè)方法可以接受任何形式的可迭代對(duì)象作為參數(shù)契耿, 甚至包括不可變序列或生成器
2.10 本章小結(jié)
要想寫(xiě)出準(zhǔn)確瞒大、 高效和地道的 Python 代碼, 對(duì)標(biāo)準(zhǔn)庫(kù)里的序列類型的掌握是不可或缺的搪桂。
Python 序列類型最常見(jiàn)的分類就是可變和不可變序列透敌。 但另外一種分類方式也很有用, 那就是把它們分為扁平序列和容器序列踢械。 前者的體積更小酗电、 速度更快而且用起來(lái)更簡(jiǎn)單, 但是它只能保存一些原子性的數(shù)據(jù)内列,比如數(shù)字撵术、 字符和字節(jié)。 容器序列則比較靈活德绿, 但是當(dāng)容器序列遇到可變對(duì)象時(shí)荷荤, 用戶就需要格外小心了, 因?yàn)檫@種組合時(shí)常會(huì)搞出一些“意外”移稳, 特別是帶嵌套的數(shù)據(jù)結(jié)構(gòu)出現(xiàn)時(shí)蕴纳, 用戶要多費(fèi)一些心思來(lái)保證代碼的正確。
列表推導(dǎo)和生成器表達(dá)式則提供了靈活構(gòu)建和初始化序列的方式个粱, 這兩個(gè)工具都異常強(qiáng)大古毛。 如果你還不能熟練地使用它們, 可以專門(mén)花時(shí)間練習(xí)一下。 它們其實(shí)不難稻薇, 而且用起來(lái)讓人上癮元組在 Python 里扮演了兩個(gè)角色嫂冻, 它既可以用作無(wú)名稱的字段的記錄,又可以看作不可變的列表塞椎。 當(dāng)元組被當(dāng)作記錄來(lái)用的時(shí)候桨仿, 拆包是最安全可靠地從元組里提取不同字段信息的方式。 新引入的*
句法讓元組拆包的便利性更上一層樓案狠, 讓用戶可以選擇性忽略不需要的字段服傍。 具名元組也已經(jīng)不是一個(gè)新概念了, 但它似乎沒(méi)有受到應(yīng)有的重視骂铁。 就像普通元組一樣吹零, 具名元組的實(shí)例也很節(jié)省空間, 但它同時(shí)提供了方便地通過(guò)名字來(lái)獲取元組各個(gè)字段信息的方式拉庵, 另外還有個(gè)實(shí)用的 ._asdict()
方法來(lái)把記錄變成 OrderedDict 類型灿椅。
Python 里最受歡迎的一個(gè)語(yǔ)言特性就是序列切片, 而且很多人其實(shí)還沒(méi)完全了解它的強(qiáng)大之處钞支。 比如茫蛹, 用戶自定義的序列類型也可以選擇支持NumPy 中的多維切片和省略( ...) 。 另外伸辟, 對(duì)切片賦值是一個(gè)修改可變序列的捷徑麻惶。
重復(fù)拼接 seq * n 在正確使用的前提下馍刮, 能讓我們方便地初始化含有
不可變?cè)氐亩嗑S列表信夫。 增量賦值 += 和 *= 會(huì)區(qū)別對(duì)待可變和不可變序列。 在遇到不可變序列時(shí)卡啰, 這兩個(gè)操作會(huì)在背后生成新的序列静稻。 但如果被賦值的對(duì)象是可變的, 那么這個(gè)序列會(huì)就地修改——然而這也取決于序列本身對(duì)特殊方法的實(shí)現(xiàn)匈辱。
序列的 sort
方法和內(nèi)置的 sorted
函數(shù)雖然很靈活振湾, 但是用起來(lái)都不
難。 這兩個(gè)方法都比較靈活亡脸, 是因?yàn)樗鼈兌冀邮芤粋€(gè)函數(shù)作為可選參數(shù)來(lái)指定排序算法如何比較大小押搪, 這個(gè)參數(shù)就是 key 參數(shù)。 key 還可以被用在 min 和 max 函數(shù)里浅碾。 如果在插入新元素的同時(shí)還想保持有序序列的順序大州, 那么需要用到bisect.insort
。 bisect.bisect
的作用則是快速查找垂谢。
除了列表和元組厦画, Python 標(biāo)準(zhǔn)庫(kù)里還有 array.array。 另外, 雖然NumPy 和 SciPy 都不是 Python 標(biāo)準(zhǔn)庫(kù)的一部分根暑, 但稍微學(xué)習(xí)一下它們力试,會(huì)讓你在處理大規(guī)模數(shù)值型數(shù)據(jù)時(shí)如有神助。