標籤:python slicing
相信即使是Python新手也很容易理解下面的切片行為:
>>> s = 'this_is_a_test'>>> s[1 : 5]'his_'
進一步,下面的文法及輸出也很不難理解:
>>> s = 'this_is_a_test'>>> s[ : : 2]'ti_sats'
那麼,下面的呢?
>>> s = 'this_is_a_test'>>> s[ : : -1]'tset_a_si_siht' ## 為何s被反轉了?>>> s[1 : 6 : -1]'' ## 為何只得到個空串?>>> s[6 : 1 : -1] 'si_si' ## 為何是這個結果?
是不是覺得當slicing運算式的第3個參數(步長,"stride" or "step")為負數時,結果有點奇怪?
說實話,新手對上述結果感到困惑是正常的(當然,如果你完全搞明白了“為什麼”,那麼恭喜你,因為你對Python文法基礎掌握的應該比較紮實了)。
本篇筆記就是要剖析slicing運算式的文法,爭取為Python初學者解開上例的困惑。
1. 切片運算式(slicing expression)
Python官方文檔對切片文法的說明非常簡潔:
A slicing selects a range of items in a sequence object (e.g., a string, tuple or list). Slicings may be used as expressions or as targets in assignment or del statements.
根據文檔說明,切片文法分為兩種:簡單切片(simple_slicing)和擴充切片(extended_slicing),下面分別進行介紹。
2. 簡單切片(simple_slicing)
簡單切片文法如下:
s[begin : end]
其中,s表示任何sequence類型的對象,begin和end必須是int值或求值後可以得到int值的運算式,它們及其之間的分號共同構成了一個前閉後開區間(即begin <= idx < end),指明切片操作將要訪問的源序列s的索引值的begin和end,其中真正的end位置是s中end的前一個位置,也即,切片返回的序列的最後一個element在源序列s的索引為(end-1)。
備忘1:begin和end都可以被預設,此時,它們的預設值分別是0和sys.maxint
備忘2:begin和end的值可以超過序列的實際長度,如s = [1, 2, 3]; s[0 : 8]也是合法的
備忘3:begin和end均可以為負值。按照Python解譯器內部實現的約定,-1是序列s最後一個元素的索引,-2是倒數第二個元素的索引,以此類推。我們可以藉助Python官方教程strings說明文檔關於slice的部分來理解正/負索引號的對應關係(注意,下面的對原文檔中索引值畫的位置有所微調,個人認為這樣更易於大多數人的理解):
由可知,
Python內部表示sequence類型時,若採用正索引值表示,則有效索引範圍是[0, len(s)-1];若採用負索引值表示,則有效索引範圍是[-len(s), -1]。
如果能牢牢記住這一點,那關於slice的一些看起來讓人疑惑的文法行為就比較容易理解了。
下面的slicing運算式均屬於簡單切片文法:
>>> s = ['a', 'b', 'c', 'd', 'e', 'f']>>> s[0 : len(s)] ## output: ['a', 'b', 'c', 'd', 'e', 'f'] >>> s[0 : -1] ## output: ['a', 'b', 'c', 'd', 'e']>>> s[ : -1] ## output: ['a', 'b', 'c', 'd', 'e'] >>> s[2 : -1] ## output: ['c', 'd', 'e']>>> s[-3 : -1] ## output: ['d', 'e']>>> s[-3 : ] ## output: ['d', 'e', 'f']>>> s[3 : 1] ## output: []
3. 擴充切片(extended_slicing)
擴充切片文法如下:
s[begin : end : stride]
與簡單切片文法相比,擴充切片只是增加了第3個參數,即步長參數(英文資料中通常稱為"stride"或"step")。
擴充切片文法引入的"stride"參數是個需要特別注意的參數,因為它的正/負取值將會影響切片操作對源序列s的訪問方向,而這正是本文開始那幾個樣本可能引起Python新手困惑的原因。
其實規則很簡單,說穿不值一文錢:
1) 當stride參數為正值(positive)時,表明切片操作從左至右(即正向)訪問源序列s的元素,此時,若begin和end參數有預設,則Python解譯器預設將其設定為None。如s[0 : : 1]會被解譯器當作s[0 : None : 1],此時,end實際取值要大於其有效索引範圍的上限值,以保證切片操作能訪問到源序列s從begin開始的所有元素(從左向右)。
2) 當stride參數為負值(negative)時,表明切片操作從右至左(即逆向)訪問源序列s的元素,此時,若begin和end參數有預設,則Python解譯器預設將其設定為None。如s[-1 : : -1]會被解譯器當作s[-1 : None : -1],此時,end實際取值要小於其有效索引範圍的下限值,已保證切片操作能訪問到源序列s從begin開始的所有元素(逆向,從右向左)。
3) 無論stride參數取正值還是負值,切片運算式的begin和end索引值需要保證在切片操作的訪問方向上,從begin到end之間有元素,這樣切片操作才能保證返回非空集。
通過本文第2節給出的Python內部對序列對象索引值約定,應該可以很容易地理解這3條規則。
介紹到這裡,本文開始處的那個樣本輸出結果的原因想必也很清楚了吧。
>>> s = 'this_is_a_test'>>> s[ : : -1]'tset_a_si_siht' ## Q: 為何s被反轉了? A: 步長參數-1表明逆向訪問s>>> s[1 : 6 : -1]'' ## Q: 為何只得到個空串? A: 步長參數為負時,逆向訪問,但begin與end間無元素,故返空串>>> s[6 : 1 : -1] 'si_si' ## Q: 為何是這個結果? A: 逆向訪問,切片訪問到的元素正值索引範圍為[2:6],故返'si_si'
備忘:根據《Python核心編程》一書第6.1.2節的介紹,這裡稱為“擴充”是因為早期的Python解譯器只支援不帶步長參數的簡單切片,帶步長的切片操作是以Python擴充的方式實現的。事實上,目前的CPython解譯器早就支援純Python的帶步長參數的切片操作,但作為曆史名稱,“擴充”一詞被保留下來。
【參考資料】
1. Python Docs: slicings
2. Python Docs: An Informal Introduction to Python - strings
3. Section 6.1.2 & 6.3.2 of <Core Python Programming>,即《Python核心編程》一書第6.1.2節和6.3.2節
4. StackOverflow: Explain Python‘s slice notation
========================= EOF ======================
【Python筆記】剖析Python的切片(slicing)文法