python每日一類(4):slice


class slice(stop)class slice(startstop[, step])

Return a slice object representing the set of indices specified by range(start, stop, step). The start and step arguments default to None. Slice objects have read-only data attributes startstop and step which merely return the argument values (or their default). They have no other explicit functionality; however they are used by Numerical Python and other third party extensions. Slice objects are also generated when extended indexing syntax is used. For example: a[start:stop:step] or a[start:stop, i]. See itertools.islice() for an alternate version that returns an iterator.

 

問題的起因

    今天在寫代碼的時候,看到一個比較有意思的寫法。假設我們有一個list,它的內容是a = [0, 1, 2, 3, 4, 5, 6, 7, 8 ,9]。如果我們取它反轉后的結果,一般我們頭腦里默認想到的無非就是reverse這樣的方法了。但是它還有一種寫法:a[::-1],輸出的結果是和當前的結果相反。在某些情況下,它的應用還是比較有意思的。就想針對這一塊總結一下。

slice在python中的應用

     在Python中,list, tuple以及字符串等可以遍歷訪問的類型都可以應用slice訪問。slice本身的意思是指切片,在這些可以遍歷訪問的類型中截取其中的某些部分。比如如下的代碼:

>>> l = range(10)  
>>> l  
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]  
>>> l[1:5]  
[1, 2, 3, 4]  

  

 首先,我們通過range(10) 生成一個從0到9的列表。在這個列表中取[1:5]的時候返回的是索引1到4的。所以,我們發現他們所取的slice是一個半開半閉的區間。l[a:b]==> l[a, b).

    前面這種情況下,是我們已知列表的長度,然后取他們的某個區段,如果我們不知道列表的長度,或者列表長度的獲取比較麻煩呢?如果用其他的語言,我們可能考慮這個列表是否應該有一個list.length之類的屬性了。在這里,有另外一個辦法來取得:

>>> l[-1]  
9  
>>> l[1:-1]  
[1, 2, 3, 4, 5, 6, 7, 8]  
>>> l[2:-2]  
[2, 3, 4, 5, 6, 7]  

  

我們可以看到如果要取列表中的最后一個元素,可以用l[-1]的方式,如果從后面向前,可以依次取l[-2], l[-3]...

    既然我們前面提到,在列表中slice是取的一個前面閉合后面開放的區間,也就是說我在l[a:b]的時候,索引值為b的那個元素是不包含在結果中的。如果我們想要包含后面的值那么該怎么辦呢?

    這個問題可以分為幾種情況來考慮,一個是加入b本身長度比較小,那么我們取l[a:b+1]就好了。比如說如下:

>>> l[1:3]  
[1, 2]  
>>> l[1:4]  
[1, 2, 3]  

   如果我們想把索引值為3的也包含進來,我們就用l[1:4]就行了。那么,對於處在列表末尾的元素呢?用過c, Java開發的人會想到,按照這種方式會不會導致訪問數組越界呢?我們試試看吧:

>>> len(l)  
10  
>>> l[1:10]  
[1, 2, 3, 4, 5, 6, 7, 8, 9]  
>>> l[1:11]  
[1, 2, 3, 4, 5, 6, 7, 8, 9]  
>>> l[1:12]  
[1, 2, 3, 4, 5, 6, 7, 8, 9]  

  

 len(l)返回l的長度。我們原來潛意識的認為,既然數組長度為10,那么我們訪問的索引最大值也不過為l[9]。實際上,在python這里,可以列出的訪問下標值超出數組長度范圍,只不過僅僅返回能遍歷到的元素而已。

    當然,我們還有另外一種辦法:

>>> l[1:]  
[1, 2, 3, 4, 5, 6, 7, 8, 9]  

  

這種方式就完全將前面索引到數組末尾的元素都包含進來了。

    這樣,我們要包含整個數組中的元素就可以采用如下的幾種方式:

>>> l[0:]  
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]  
>>> l[:]  
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]  
>>> l  
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]  

   從前面我們用l[a:b] 的方式來訪問元素來看,我們這里a, b取的值要么滿足0<= a <= b 或者 a >= 0 b < 0。實際上,a所對應元素的位置總是在b所對應位置的后面。那么,如果我們把他們的順序倒過來一下會怎么樣呢?比如說:

>>> l[5:2]  
[]  
>>> l[-1:3]  
[]  

  

   在這里,我們發現,如果實際取的元素先從右邊開始然后到左邊的話,並不是我們所期望的返回一個倒過來的數組,而是返回一個空的數組。我舉的這個例子有什么用呢?別急,看了后面那一節你就知道了。

理解extended slice

    前面那一部分相對來說還是比較好理解的。現在,如果我們有一些其他的要求,比如說,我們想返回數組里面索引為奇數的元素,或者索引為偶數的元素,那么該怎么辦呢?

我們可以有幾種辦法來做,其中的一種就是采用extended slice,一個典型的解決方法如下:

>>> l[::2]  
[0, 2, 4, 6, 8]  
>>> l[1::2]  
[1, 3, 5, 7, 9]  
>>>   

前面這種包含兩個冒號的樣式是怎么回事呢?

     實際上,我們這邊第一個冒號隔開的這兩個部分和前面的意思是一樣的,就是指定數組中間元素的區間。所以前面第一個l[::2]前面就是指的整個數組的元素。而后面那個部分則是指的一個步長。這表示什么意思呢?就是既然我們前面指定的是整個數組,那么它就是從0開始,然后每次訪問后面相鄰的元素。而設置為2之后呢,則訪問后面和它距離為2的元素,而不是直接相鄰的元素。這樣,我們也就容易理解l[1::2],它就是從元素1開始到結尾的元素集合里取間隔為2的這些元素。

    到這一步,就離我們理解前面那個古怪的l[::-1]很接近了。我們前面的這個取步長是將步長設置為正數,所以在取元素的集合里它表示從左到右的取指定步長覆蓋的元素。如果我們將步長設置為負數呢?我們來看:

>>> l[1:9:-1]  
[]  
>>> l[9:1:-1]  
[9, 8, 7, 6, 5, 4, 3, 2]  

  

 有了前面這一部分的代碼,相信就不難理解了。我們取區間[1, 9),結果取步長為-1的時候返回的是一個空的集合。而我們取9到1的時候,步長為-1取出來了倒序的數組。這是因為如果我們指定的步長為負數的話,那么它必須和數據指定的區間方向一致。也就是說,如果我們前面指定的區間是從數組小的索引到大的索引,那么我指定的步長必然也要從小到大。所以必須為正數。而如果我們指定的區間是從后面往前的話,則步長必須指定為負數。否則返回的結果都是空的數組。

總結

    有了前面那么多的討論,我們再來看數組的slice訪問。他們無非就是這么幾個情況,在l[a:b]的情況下,必須保證a所在的索引位置在前,b所在的索引位置在后,否則返回結果為空。在l[a:b:step]的情況下,我們首先要根據a, b的位置來判斷方向,a在前,b在后的話,step應該為正,否則應該為負。不符合這些情況的話,則返回空的數組。也就是說,看a, b的位置來確定方向,不要犯方向性的錯誤,否則就竹籃打水一場空了:)

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM