眾所周知业汰,我們可以通過索引值(或稱下標(biāo))來查找序列類型(如字符串、列表菩颖、元組…)中的單個元素样漆,那么,如果要獲取一個索引區(qū)間的元素該怎么辦呢晦闰?
切片(slice)就是一種截取索引片段的技術(shù)放祟,借助切片技術(shù),我們可以十分靈活地處理序列類型的對象呻右。通常來說跪妥,切片的作用就是截取序列對象,然而声滥,對于非序列對象眉撵,我們是否有辦法做到切片操作呢?在使用切片的過程中,有什么要點值得重視执桌,又有什么底層原理值得關(guān)注呢鄙皇?本文將主要跟大家一起來探討這些內(nèi)容,希望我能與你共同學(xué)習(xí)進(jìn)步仰挣。
小編推薦大家可以加我的扣扣群 735934841 伴逸,免費領(lǐng)取Python 學(xué)習(xí)資料
1、切片的基礎(chǔ)用法
列表是 Python 中極為基礎(chǔ)且重要的一種數(shù)據(jù)結(jié)構(gòu)膘壶,也是最能發(fā)揮切片的用處的一種數(shù)據(jù)結(jié)構(gòu)错蝴,所以在前兩節(jié),我將以列表為例介紹切片的一些常見用法颓芭。
首先是切片的書寫形式:[i : i+n : m] 顷锰;其中,i 是切片的起始索引值亡问,為列表首位時可省略官紫;i+n 是切片的結(jié)束位置,為列表末位時可省略州藕;m 可以不提供束世,默認(rèn)值是1,不允許為0?床玻,當(dāng)m為負(fù)數(shù)時毁涉,列表翻轉(zhuǎn)。注意:這些值都可以大于列表長度锈死,不會報越界贫堰。
切片的基本含義是:從序列的第i位索引起,向右取到后n位元素為止待牵,按m間隔過濾?其屏。
li?=?[1,4,5,6,7,9,11,14,16]
#?以下寫法都可以表示整個列表,其中?X?>=?len(li)
li[0:X]?==?li[0:]?==?li[:X]?==?li[:]
==?li[::]?==?li[-X:X]?==?li[-X:]
li[1:5]?==?[4,5,6,7]#?從1起缨该,取5-1位元素
li[1:5:2]?==?[4,6]#?從1起漫玄,取5-1位元素,按2間隔過濾
li[-1:]?==?[16]#?取倒數(shù)第一個元素
li[-4:-2]?==?[9,11]#?從倒數(shù)第四起压彭,取-2-(-4)=2位元素
li[:-2]?==?li[-len(li):-2]
==?[1,4,5,6,7,9,11]#?從頭開始睦优,取-2-(-len(li))=7位元素
#?步長為負(fù)數(shù)時,列表先翻轉(zhuǎn)壮不,再截取
li[::-1]?==?[16,14,11,9,7,6,5,4,1]#?翻轉(zhuǎn)整個列表
li[::-2]?==?[16,11,7,5,1]#?翻轉(zhuǎn)整個列表汗盘,再按2間隔過濾
li[:-5:-1]?==?[16,14,11,9]#?翻轉(zhuǎn)整個列表,取-5-(-len(li))=4位元素
li[:-5:-3]?==?[16,9]#?翻轉(zhuǎn)整個列表询一,取-5-(-len(li))=4位元素隐孽,再按3間隔過濾
#?切片的步長不可以為0
li[::0]#?報錯(ValueError:?slice?step?cannot?be?zero)
上述的某些例子對于初學(xué)者(甚至很多老手)來說癌椿,可能還不好理解,但是它們都離不開切片的基本語法菱阵,所以為方便起見踢俄,我將它們也歸入基礎(chǔ)用法中。
對于這些樣例晴及,我個人總結(jié)出兩條經(jīng)驗:
(1)牢牢記住公式[i : i+n : m]都办,當(dāng)出現(xiàn)缺省值時,通過想象把公式補(bǔ)全虑稼;
(2)索引為負(fù)且步長為正時琳钉,按倒數(shù)計算索引位置;索引為負(fù)且步長為負(fù)時蛛倦,先翻轉(zhuǎn)列表歌懒,再按倒數(shù)計算索引位置。
2、切片的高級用法
一般而言,切片操作的返回結(jié)果是一個新的獨立的序列薪寓。以列表為例,列表切片后得到的還是一個列表验烧,占用新的內(nèi)存地址。
當(dāng)取出切片的結(jié)果時钾虐,它是一個獨立對象,因此笋庄,可以將其用于賦值操作效扫,也可以用于其它傳遞值的場景。但是直砂,切片只是淺拷貝?菌仁,它拷貝的是原列表中元素的引用,所以静暂,當(dāng)存在變長對象的元素時济丘,新列表將受制于原列表。
li?=?[1,?2,?3,?4]
ls?=?li[::]
li?==?ls#?True
id(li)?==?id(ls)#?False
li.append(li[2:4])?#?[1,?2,?3,?4,?[3,?4]]
ls.extend(ls[2:4])?#?[1,?2,?3,?4,?3,?4]
#?下例等價于判斷l(xiāng)i長度是否大于8
if(li[8:]):
print("not?empty")
else:
print("empty")
#?切片列表受制于原列表
lo?=?[1,[1,1],2,3]
lp?=?lo[:2]#?[1,?[1,?1]]
lo[1].append(1)#?[1,?[1,?1,?1],?2,?3]
lp#?[1,?[1,?1,?1]]
由于可見洽蛀,將切片結(jié)果取出摹迷,它可以作為獨立對象使用,但是也要注意郊供,是否取出了變長對象的元素峡碉。
切片既可以作為獨立對象被“取出”原序列,也可以留在原序列驮审,作為一種占位符使用鲫寄。
不久前吉执,我介紹了幾種拼接字符串的方法(鏈接見文末),其中三種格式化類的拼接方法(即 %地来、format()戳玫、template)就是使用了占位符的思想。對于列表來說未斑,使用切片作為占位符咕宿,同樣能夠?qū)崿F(xiàn)拼接列表的效果。特別需要注意的是颂碧,給切片賦值的必須是可迭代對象荠列。
li?=?[1,?2,?3,?4]
#?在頭部拼接
li[:0]?=?[0]?#?[0,?1,?2,?3,?4]
#?在末尾拼接
li[len(li):]?=?[5,7]?#?[0,?1,?2,?3,?4,?5,?7]
#?在中部拼接
li[6:6]?=?[6]?#?[0,?1,?2,?3,?4,?5,?6,?7]
#?給切片賦值的必須是可迭代對象
li[-1:-1]?=?6?#?(報錯,TypeError:?can?only?assign?an?iterable)
li[:0]?=?(9,)?#??[9,?0,?1,?2,?3,?4,?5,?6,?7]
li[:0]?=?range(3)?#??[0,?1,?2,?9,?0,?1,?2,?3,?4,?5,?6,?7]
上述例子中载城,若將切片作為獨立對象取出肌似,那你會發(fā)現(xiàn)它們都是空列表,即li[:0]==li[len(li):]==li[6:6]==[]?诉瓦,我將這種占位符稱為“純占位符”川队,對純占位符賦值,并不會破壞原有的元素睬澡,只會在特定的索引位置中拼接進(jìn)新的元素固额。刪除純占位符時,也不會影響列表中的元素煞聪。
與“純占位符”相對應(yīng)斗躏,“非純占位符”的切片是非空列表,對它進(jìn)行操作(賦值與刪除)昔脯,將會影響原始列表啄糙。如果說純占位符可以實現(xiàn)列表的拼接,那么云稚,非純占位符可以實現(xiàn)列表的替換隧饼。
li=?[1,2,3,4]
#?不同位置的替換
li[:3]?=?[7,8,9]#?[7,?8,?9,?4]
li[3:]?=?[5,6,7]#?[7,?8,?9,?5,?6,?7]
li[2:4]?=?['a','b']#?[7,?8,?'a',?'b',?6,?7]
#?非等長替換
li[2:4]?=?[1,2,3,4]#?[7,?8,?1,?2,?3,?4,?6,?7]
li[2:6]?=?['a']#?[7,?8,?'a',?6,?7]
#?刪除元素
del?li[2:3]#?[7,?8,?6,?7]
切片占位符可以帶步長,從而實現(xiàn)連續(xù)跨越性的替換或刪除效果静陈。需要注意的是燕雁,這種用法只支持等長替換。
li?=?[1,2,3,4,5,6]
li[::2]?=?['a','b','c']?#?['a',2,'b',4,'c',6]
li[::2]?=?[0]*3#?[0,2,0,4,0,6]
li[::2]?=?['w']?#?報錯鲸拥,attempttoassign?sequenceofsize1toextended?sliceofsize3
del?li[::2]?#?[2,4,6]
3拐格、自定義對象實現(xiàn)切片功能
切片是 Python 中最迷人最強(qiáng)大最 Amazing 的語言特性(幾乎沒有之一),以上兩小節(jié)雖然介紹了切片的基礎(chǔ)用法與高級用法刑赶,但這些還不足以充分地展露切片的魅力禁荒,所以,在接下來的兩章節(jié)中角撞,我們將聚焦于它的更高級用法呛伴。
前兩節(jié)內(nèi)容都是基于原生的序列類型(如字符串勃痴、列表、元組……)热康,那么沛申,我們是否可以定義自己的序列類型并讓它支持切片語法呢?更進(jìn)一步姐军,我們是否可以自定義其它對象(如字典)并讓它支持切片呢铁材?
3.1、魔術(shù)方法:`getitem()`
想要使自定義對象支持切片語法并不難奕锌,只需要在定義類的時候給它實現(xiàn)魔術(shù)方法__getitem__()?即可著觉。所以,這里就先介紹一下這個方法惊暴。
語法:object.__getitem__(self, key)
官方文檔釋義:Called to implement evaluation of self[key]. For sequence types, the accepted keys should be integers and slice objects. Note that the special interpretation of negative indexes (if the class wishes to emulate a sequence type) is up to the?__getitem__()?method. If key is of an inappropriate type, TypeError may be raised; if of a value outside the set of indexes for the sequence (after any special interpretation of negative values), IndexError should be raised. For mapping types, if key is missing (not in the container), KeyError should be raised.
概括翻譯一下:__getitem__()?方法用于返回參數(shù) key 所對應(yīng)的值饼丘,這個 key 可以是整型數(shù)值和切片對象,并且支持負(fù)數(shù)索引辽话;如果 key 不是以上兩種類型肄鸽,就會拋 TypeError;如果索引越界油啤,會拋 IndexError 典徘;如果定義的是映射類型,當(dāng) key 參數(shù)不是其對象的鍵值時益咬,則會拋 KeyError 逮诲。
3.2、自定義序列實現(xiàn)切片功能
接下來幽告,我們定義一個簡單的 MyList 梅鹦,并給它加上切片功能。(PS:僅作演示评腺,不保證其它功能的完備性)帘瞭。
importnumbers
classMyList():
def__init__(self,?anylist):
self.data?=?anylist
def__len__(self):
returnlen(self.data)
def__getitem__(self,?index):
print("key?is?:?"+?str(index))
cls?=?type(self)
ifisinstance(index,?slice):
print("data?is?:?"+?str(self.data[index]))
returncls(self.data[index])
elifisinstance(index,?numbers.Integral):
returnself.data[index]
else:
msg?="{cls.__name__}?indices?must?be?integers"
raiseTypeError(msg.format(cls=cls))
l?=?MyList(["My","name","is","Python貓"])
###?輸出結(jié)果:
keyis:3
Python貓
keyis:?slice(None,2,None)
datais:?['My','name']
<__main__.MyList?object?at0x0000019CD83A7A90>
keyis:?hi
Traceback?(most?recent?call?last):
...
TypeError:?MyList?indices?must?be?integersorslices
從輸出結(jié)果來看淑掌,自定義的 MyList 既支持按索引查找蒿讥,也支持切片操作,這正是我們的目的抛腕。
3.3芋绸、自定義字典實現(xiàn)切片功能
切片是序列類型的特性,所以在上例中担敌,我們不需要寫切片的具體實現(xiàn)邏輯摔敛。但是,對于其它非序列類型的自定義對象全封,就得自己實現(xiàn)切片邏輯马昙。以自定義字典為例(PS:僅作演示桃犬,不保證其它功能的完備性):
classMyDict():
def__init__(self):
self.data?=?{}
def__len__(self):
returnlen(self.data)
defappend(self,?item):
self.data[len(self)]?=?item
def__getitem__(self,?key):
ifisinstance(key,?int):
returnself.data[key]
ifisinstance(key,?slice):
slicedkeys?=?list(self.data.keys())[key]
return{k:?self.data[k]forkinslicedkeys}
else:
raiseTypeError
d?=?MyDict()
d.append("My")
d.append("name")
d.append("is")
d.append("Python貓")
print(d[2])
print(d[:2])
print(d[-4:-2])
print(d['hi'])
###?輸出結(jié)果:
is
{0:'My',1:'name'}
{0:'My',1:'name'}
Traceback?(most?recent?call?last):
...
TypeError
上例的關(guān)鍵點在于將字典的鍵值取出,并對鍵值的列表做切片處理行楞,其妙處在于攒暇,不用擔(dān)心索引越界和負(fù)數(shù)索引,將字典切片轉(zhuǎn)換成了字典鍵值的切片子房,最終實現(xiàn)目的形用。
4、迭代器實現(xiàn)切片功能
好了证杭,介紹完一般的自定義對象如何實現(xiàn)切片功能田度,這里將迎來另一類非同一般的對象。
迭代器是 Python 中獨特的一種高級對象解愤,它本身不具備切片功能镇饺,然而若能將它用于切片,這便仿佛是錦上添花琢歇,能達(dá)到如虎添翼的效果兰怠。所以,本節(jié)將隆重地介紹迭代器如何實現(xiàn)切片功能李茫。
4.1揭保、迭代與迭代器
首先,有幾個基本概念要澄清:迭代魄宏、可迭代對象秸侣、迭代器。
迭代?是一種遍歷容器類型對象(例如字符串宠互、列表味榛、字典等等)的方式,例如予跌,我們說迭代一個字符串“abc”搏色,指的就是從左往右依次地、逐個地取出它的全部字符的過程券册。(PS:漢語中迭代一詞有循環(huán)反復(fù)频轿、層層遞進(jìn)的意思,但 Python 中此詞要理解成單向水平線性?的烁焙,如果你不熟悉它航邢,我建議直接將其理解為遍歷。)
那么骄蝇,怎么寫出迭代操作的指令呢膳殷?最通用的書寫語法就是 for 循環(huán)。
#?for循環(huán)實現(xiàn)迭代過程
forcharin"abc":
print(char,?end="?")
#?輸出結(jié)果:a?b?c
for 循環(huán)可以實現(xiàn)迭代的過程九火,但是赚窃,并非所有對象都可以用于 for 循環(huán)册招,例如,上例中若將字符串“abc”換成任意整型數(shù)字勒极,則會報錯: 'int' object is not iterable .
這句報錯中的單詞“iterable”指的是“可迭代的”跨细,即 int 類型不是可迭代的。而字符串(string)類型是可迭代的河质,同樣地冀惭,列表、元組掀鹅、字典等類型散休,都是可迭代的。
那怎么判斷一個對象是否可迭代呢乐尊?為什么它們是可迭代的呢戚丸?怎么讓一個對象可迭代呢?
要使一個對象可迭代扔嵌,就要實現(xiàn)可迭代協(xié)議限府,即需要實現(xiàn)__iter__()?魔術(shù)方法,換言之痢缎,只要實現(xiàn)了這個魔術(shù)方法的對象都是可迭代對象胁勺。
那怎么判斷一個對象是否實現(xiàn)了這個方法呢?除了上述的 for 循環(huán)外独旷,我還知道四種方法:
#?方法1:dir()查看__iter__
dir(2)#?沒有署穗,略
dir("abc")#?有,略
#?方法2:isinstance()判斷
importcollections
isinstance(2,?collections.Iterable)#?False
isinstance("abc",?collections.Iterable)#?True
#?方法3:hasattr()判斷
hasattr(2,"__iter__")#?False
hasattr("abc","__iter__")#?True
#?方法4:用iter()查看是否報錯
iter(2)#?報錯:'int'?object?is?not?iterable
iter("abc")#?<str_iterator?at?0x1e2396d8f28>
###?PS:判斷是否可迭代嵌洼,還可以查看是否實現(xiàn)__getitem__案疲,為方便描述,本文從略麻养。
這幾種方法中最值得一提的是 iter() 方法褐啡,它是 Python 的內(nèi)置方法,其作用是將可迭代對象變成迭代器?鳖昌。這句話可以解析出兩層意思:(1)可迭代對象跟迭代器是兩種東西备畦;(2)可迭代對象能變成迭代器。
實際上遗遵,迭代器必然是可迭代對象萍恕,但可迭代對象不一定是迭代器逸嘀。兩者有多大的區(qū)別呢车要?
如上圖藍(lán)圈所示,普通可迭代對象與迭代器的最關(guān)鍵區(qū)別可概括為:一同兩不同?崭倘,所謂“一同”翼岁,即兩者都是可迭代的(__iter__)类垫,所謂“兩不同”,即可迭代對象在轉(zhuǎn)化為迭代器后琅坡,它會丟失一些屬性(__getitem__)悉患,同時也增加一些屬性(__next__)。
首先看看增加的屬性?__next__?榆俺, 它是迭代器之所以是迭代器的關(guān)鍵售躁,事實上,我們正是把同時實現(xiàn)了?__iter__?方法 和?__next__?方法的對象定義為迭代器的茴晋。
有了多出來的這個屬性陪捷,可迭代對象不需要借助外部的 for 循環(huán)語法,就能實現(xiàn)自我的迭代/遍歷過程诺擅。我發(fā)明了兩個概念來描述這兩種遍歷過程(PS:為了易理解市袖,這里稱遍歷,實際也可稱為迭代):它遍歷?指的是通過外部語法而實現(xiàn)的遍歷烁涌,自遍歷?指的是通過自身方法實現(xiàn)的遍歷苍碟。
借助這兩個概念,我們說撮执,可迭代對象就是能被“它遍歷”的對象微峰,而迭代器是在此基礎(chǔ)上,還能做到“自遍歷”的對象抒钱。
ob1?="abc"
ob2?=?iter("abc")
ob3?=?iter("abc")
#?ob1它遍歷
foriinob1:
print(i,?end?="?")#?a?b?c
foriinob1:
print(i,?end?="?")#?a?b?c
#?ob1自遍歷
ob1.__next__()#?報錯:?'str'?object?has?no?attribute?'__next__'
#?ob2它遍歷
foriinob2:
print(i,?end?="?")#?a?b?c????
foriinob2:
print(i,?end?="?")#?無輸出
#?ob2自遍歷
ob2.__next__()#?報錯:StopIteration
#?ob3自遍歷
ob3.__next__()#?a
ob3.__next__()#?b
ob3.__next__()#?c
ob3.__next__()#?報錯:StopIteration
通過上述例子可看出县忌,迭代器的優(yōu)勢在于支持自遍歷,同時继效,它的特點是單向非循環(huán)的症杏,一旦完成遍歷,再次調(diào)用就會報錯瑞信。
對此厉颤,我想到一個比方:普通可迭代對象就像是子彈匣,它遍歷就是取出子彈凡简,在完成操作后又裝回去逼友,所以可以反復(fù)遍歷(即多次調(diào)用for循環(huán),返回相同結(jié)果)秤涩;而迭代器就像是裝載了子彈匣且不可拆卸的槍帜乞,進(jìn)行它遍歷或者自遍歷都是發(fā)射子彈,這是消耗性的遍歷筐眷,是無法復(fù)用的(即遍歷會有盡頭)黎烈。
寫了這么多,稍微小結(jié)一下:迭代是一種遍歷元素的方式,按照實現(xiàn)方式劃分照棋,有外部迭代與內(nèi)部迭代兩種资溃,支持外部迭代(它遍歷)的對象就是可迭代對象,而同時還支持內(nèi)部迭代(自遍歷)的對象就是迭代器烈炭;按照消費方式劃分溶锭,可分為復(fù)用型迭代與一次性迭代,普通可迭代對象是復(fù)用型的符隙,而迭代器是一次性的趴捅。
4.2、迭代器切片
前面提到了“一同兩不同”霹疫,最后的不同是驻售,普通可迭代對象在轉(zhuǎn)化成迭代器的過程中會丟失一些屬性,其中關(guān)鍵的屬性是?__getitem__?更米。在前一節(jié)中欺栗,我已經(jīng)介紹了這個魔術(shù)方法,并用它實現(xiàn)了自定義對象的切片特性征峦。
那么問題來了:為啥迭代器不繼承這個屬性呢迟几?
首先,迭代器使用的是消耗型的遍歷栏笆,這意味著它充滿不確定性类腮,即其長度與索引鍵值對是動態(tài)衰減的,所以很難 get 到它的 item 蛉加,也就不再需要__getitem__?屬性了蚜枢。其次,若強(qiáng)行給迭代器加上這個屬性针饥,這并不合理厂抽,正所謂強(qiáng)扭的瓜不甜……
由此,新的問題來了:既然會丟失這么重要的屬性(還包括其它未標(biāo)識的屬性)丁眼,為什么還要使用迭代器呢筷凤?
這個問題的答案在于,迭代器擁有不可替代的強(qiáng)大的有用的功能苞七,使得 Python 要如此設(shè)計它藐守。限于篇幅,此處不再展開蹂风,后續(xù)我會專門填坑此話題卢厂。
還沒完,死纏爛打的問題來了:能否令迭代器擁有這個屬性呢惠啄,即令迭代器繼續(xù)支持切片呢慎恒?
hi?="歡迎關(guān)注公眾號:Python貓"
it?=?iter(hi)
#?普通切片
hi[-7:]#?Python貓
#?反例:迭代器切片
it[-7:]#?報錯:'str_iterator'?object?is?not?subscriptable
迭代器因為缺少__getitem__?任内,因此不能使用普通的切片語法。想要實現(xiàn)切片巧号,無非兩種思路:一是自己造輪子,寫實現(xiàn)的邏輯姥闭;二是找到封裝好的輪子丹鸿。
Python 的 itertools 模塊就是我們要找的輪子,用它提供的方法可輕松實現(xiàn)迭代器切片棚品。
importitertools
#?例1:簡易迭代器
s?=?iter("123456789")
forxinitertools.islice(s,2,6):
print(x,?end?="?")#?輸出:3?4?5?6
forxinitertools.islice(s,2,6):
print(x,?end?="?")#?輸出:9
#?例2:斐波那契數(shù)列迭代器
classFib():
def__init__(self):
self.a,?self.b?=1,1
def__iter__(self):
whileTrue:
yieldself.a
self.a,?self.b?=?self.b,?self.a?+?self.b
f?=?iter(Fib())
forxinitertools.islice(f,2,6):
print(x,?end?="?")#?輸出:2?3?5?8
forxinitertools.islice(f,2,6):
print(x,?end?="?")#?輸出:34?55?89?144
itertools 模塊的 islice() 方法將迭代器與切片完美結(jié)合靠欢,終于回答了前面的問題。然而铜跑,迭代器切片跟普通切片相比门怪,前者有很多局限性。首先锅纺,這個方法不是“純函數(shù)”(純函數(shù)需遵守“相同輸入得到相同輸出”的原則)掷空;其次,它只支持正向切片囤锉,且不支持負(fù)數(shù)索引坦弟,這都是由迭代器的損耗性所決定的。
那么官地,我不禁要問:itertools 模塊的切片方法用了什么實現(xiàn)邏輯呢酿傍?下方是官網(wǎng)提供的源碼:
defislice(iterable,?*args):
#?islice('ABCDEFG',?2)?-->?A?B
#?islice('ABCDEFG',?2,?4)?-->?C?D
#?islice('ABCDEFG',?2,?None)?-->?C?D?E?F?G
#?islice('ABCDEFG',?0,?None,?2)?-->?A?C?E?G
s?=?slice(*args)
#?索引區(qū)間是[0,sys.maxsize],默認(rèn)步長是1
start,?stop,?step?=?s.startor0,?s.stoporsys.maxsize,?s.stepor1
it?=?iter(range(start,?stop,?step))
try:
nexti?=?next(it)
exceptStopIteration:
#?Consume?*iterable*?up?to?the?*start*?position.
fori,?elementinzip(range(start),?iterable):
pass
return
try:
fori,?elementinenumerate(iterable):
ifi?==?nexti:
yieldelement
nexti?=?next(it)
exceptStopIteration:
#?Consume?to?*stop*.
fori,?elementinzip(range(i?+1,?stop),?iterable):
pass
islice() 方法的索引方向是受限的驱入,但它也提供了一種可能性:即允許你對一個無窮的(在系統(tǒng)支持范圍內(nèi))迭代器進(jìn)行切片的能力赤炒。這是迭代器切片最具想象力的用途場景。
除此之外亏较,迭代器切片還有一個很實在的應(yīng)用場景:讀取文件對象中給定行數(shù)范圍的數(shù)據(jù)莺褒。
我們知道,從文件中讀取內(nèi)容主要有兩種方法(參見之前關(guān)于文件讀寫的文章):read() 適合讀取內(nèi)容較少的情況雪情,或者是需要一次性處理全部內(nèi)容的情況癣朗;而 readlines() 適用性更廣,因為它是迭代地讀取內(nèi)容旺罢,既減少內(nèi)存壓力旷余,又方便逐行對數(shù)據(jù)處理。
雖然 readlines() 有迭代讀取的優(yōu)勢扁达,但它是從頭到尾逐行讀取正卧,若文件有幾千行,而我們只想要讀取少數(shù)特定行(例如第1000-1009行)跪解,那它還是效率太低了炉旷。考慮到文件對象天然就是迭代器?,我們可以使用迭代器切片先行截取窘行,然后再處理饥追,如此效率將大大地提升。
#?test.txt?文件內(nèi)容
'''
貓
Python貓
python?is?a?cat.
this?is?the?end.
'''
fromitertoolsimportislice
withopen('test.txt','r',encoding='utf-8')asf:
print(hasattr(f,"__next__"))#?判斷是否迭代器
content?=?islice(f,2,4)
forlineincontent:
print(line.strip())
###?輸出結(jié)果:
True
pythonisa?cat.
thisisthe?end.
本節(jié)內(nèi)容較多罐盔,簡單回顧一下:迭代器是一種特殊的可迭代對象但绕,可用于它遍歷與自遍歷,但遍歷過程是損耗型的惶看,不具備循環(huán)復(fù)用性捏顺,因此,迭代器本身不支持切片操作纬黎;通過借助 itertools 模塊幅骄,我們能實現(xiàn)迭代器切片,將兩者的優(yōu)勢相結(jié)合本今,其主要用途在于截取大型迭代器(如無限數(shù)列拆座、超大文件等等)的片段,實現(xiàn)精準(zhǔn)的處理冠息,從而大大地提升性能與效率懂拾。
5、小結(jié)
最后總結(jié)一下铐达,切片是 Python 的一種高級特性岖赋,常用于截取序列類型的元素,但并不局限于此瓮孙,本文主要介紹了它的基礎(chǔ)用法唐断、高級用法(如占位符用法)、自定義對象切片杭抠、以及迭代器切片等使用內(nèi)容脸甘。除此之外,切片還有更廣闊多樣的使用場景偏灿,例如 Numpy 的多維切片丹诀、內(nèi)存視圖切片、異步迭代器切片等等翁垂,都值得我們?nèi)ヌ剿饕环?/p>
學(xué)習(xí)Python過程中會遇到很多問題铆遭,可以到我們的 python學(xué)習(xí)交流群【七 三 五,九 三 四沿猜,八 四 一】枚荣,基礎(chǔ),進(jìn)階啼肩。從企業(yè)招聘人才需求 到怎么學(xué)習(xí)python橄妆,和學(xué)習(xí)什么內(nèi)容都有免費系統(tǒng)分享衙伶。希望可以幫助你快速了解Python,學(xué)習(xí)python