­

python slice的幾個小點總結

最近在看python時發現python中關於序列的操作,尤其slice的用法挺特別的,遂上網又細細查了查資料,感覺這篇文章總結的很好,就轉載下來,留個記錄。原文地址

問題的起因

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

slice在python中的應用

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

Python代碼 

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

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

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

Python代碼 

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

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

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

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

Python代碼  

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

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

Python代碼  

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

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

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

Python代碼  

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

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

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

Python代碼  

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

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

Python代碼  

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

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

理解extended slice

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

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

Python代碼  

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

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

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

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

Python代碼  

  1. >>> l[1:9:-1]  
  2. []  
  3. >>> l[9:1:-1]  
  4. [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的位置來確定方向,不要犯方向性的錯誤,否則就竹籃打水一場空了:)