iT邦幫忙

3

【python入門教室】(6) python 的切片語法介紹

嗨,大家好,我是心原一馬,
內心原來一心喜歡打程式碼

python是一門精簡的語言,
如果你對python很熟悉的話,
常常可以達到別的程式語言要打三行程式,
到了python只需要打一行

python中有兩個跟其它語言蠻不一樣的語法-
「切片」(slice)與「列表生成式」(List Comprehensions)

小馬認為這兩招將python精簡語法發揮到了極致,
首先先學習什麼是 「切片」(slice)

Review- python的列表如何取得元素?

在列表名稱後面加上中括號[],裡面填入位置編號,
即可取出資料值,例如我們有一個列表,表示早餐店的菜單:

L = ["香雞堡", "豬肉堡", "牛肉堡", "總匯三明治", "鮪魚三明治", "火腿三明治"]
>>> L[0]
"香雞堡"
>>> L[1]
"豬肉堡"
>>> L[-1]
"火腿三明治"

L[0]是從左邊數起的第一個元素,
L[1]是從左邊數起的第二個元素,

L[-1]是L的倒數第一個元素,
L[-2]是L的倒數第二個元素,

以此類推

切片介紹

那如果我們想要取得前三個元素所形成的列表呢?
一樣L = ["香雞堡", "豬肉堡", "牛肉堡", "總匯三明治", "鮪魚三明治", "火腿三明治"]
我們想要得到子列表["香雞堡", "豬肉堡", "牛肉堡"]

方法一、直接寫

最普通的方法便是直接取值:

foods= [L[0], L[1], L[2]] # ["香雞堡", "豬肉堡", "牛肉堡"]

可是假如陣列很大,
我們想要取的是前一百個元素,
抑或要取前i個元素(i是變數),
這樣寫就不切實際了

方法二、for迴圈

第二招是使用for迴圈,應該也不難想:

foods=[]
for i in range(3):
    foods.append(foodArr[i])

但是對python的精簡語法來說,用for迴圈又稍嫌繁瑣,
python的切片操作符就是用來簡化這種操作而設的

方法三、切片語法

切片操作符的基礎語法:

newArr = L[起始位置(包含) : 結束位置(不包含): 步長]

其中L可以是列表或是字串或是元組
L[起始位置(包含) : 結束位置(不包含): 步長] 的意思為,
從L這個列表,取出範圍為L[起始位置] ~ L[結束位置-1]之間的數字,
每步長個間隔取一次,
若步長這個參數省略則預設為1

也就是說,上述取出前三個元素的動作,
可以簡單用切片來取:

L=["香雞堡", "豬肉堡", "牛肉堡", "總匯三明治", "鮪魚三明治", "火腿三明治"]
foods= L[0:3]

如果第一個參數是0,可以省略:

>>> L[:3]
["香雞堡", "豬肉堡", "牛肉堡"]

若第二個參數省略,表示從起始位置直接取到列表的結束,
例如:

>>> L[3:]
['總匯三明治', '鮪魚三明治', '火腿三明治']

因此,舉一反三一下,
第一個參數和第二個參數同時省略,
就可以得到整個列表的複製(表示從第0個元素取到最後一個)

>>> L[:]
['香雞堡', '豬肉堡', '牛肉堡', '總匯三明治', '鮪魚三明治', '火腿三明治']

切片搭配負數index使用

小馬有教過可以用一個負數取得陣列倒數的元素,
L[-1]是L的倒數第一個元素,
L[-2]是L的倒數第二個元素,

因此,如果需要取得陣列倒數兩個元素,則可以這樣寫:

>>> L[-2:]
['鮪魚三明治', '火腿三明治']

試試更進階的,搭配步長做使用:

>>> L[1:5:2] #從index = 1的元素開始,每2個取一次,直到index = 5的元素(不包含)
['豬肉堡', '總匯三明治']

想要更了解切片語法的話,建議可以自己宣告一個大一點的列表,
然後試著改參數試試會更清楚

切片 v.s. range函數

這邊幫助讀者融會貫通,
不知讀者是否有發現,切片和range函數的語法其實非常相似呢?

切片操作符的基礎語法為:

newArr = L[起始位置(包含) : 結束位置(不包含): 步長]

range()函數的基礎語法為:

range(起始位置(包含), 結束位置(不包含), 步長)

兩種語法其實可以一起記
譬如說:
range(4,21,3) 表示數字範圍 4,7,10,13,16,19
L[4:21:3] 則表示切片的列表 [L[4], L[7], L[10], L[13], L[16], L[19]]

重要觀念: N = L v.s. N = L[:]

這邊宣告L = ['蛋糕', '蘋果', '香蕉']
剛剛在介紹切片語法時,有提到寫L[:]會得到切片的複製,
請看例子:

L =  ['蛋糕', '蘋果', '香蕉']
N = L[:]
print(N) # ['蛋糕', '蘋果', '香蕉']

但是寫L[:]看起來好像比較不好懂,
這時有人會問啦,
第二行可不可以乾脆寫成N = L?

事實上,N = LN = L[:]是完全不同的意思,
N = L直接賦值,會把列表L所在的記憶體位置直接給N
N = L[:]才是複製一個列表,將L的元素複製一份給N

小馬知道看文字敘述你們應該有看沒有懂,
小馬畫兩張圖應該會清楚許多

N = L的效果

若直接寫N = L,N和L兩個變數會指向同一個列表
https://ithelp.ithome.com.tw/upload/images/20200409/20117114yPxavwT45e.png

N = L[:]的效果

若寫N = L[:],則是會將L的元素複製一份給N
https://ithelp.ithome.com.tw/upload/images/20200409/20117114pKz2vhoMBC.png

還是不好懂的話,
小馬以奇幻小說的語言來比喻(不確定這樣會不會比較好懂,小馬盡力~):

  • N = L你就想成彷彿同一個身體有雙重人格 (實際只有一個身體)
  • N = L[:]你就想成是複製出一個克隆人 (會多複製出一個身體)

再給個實例應該就明瞭了

程式範例

若是N = L的情況,因為N和L指向同一個列表,修改N的元素會影響L

L =  ['蛋糕', '蘋果', '香蕉']
N = L
N[1] = '葡萄'
print(L) # ['蛋糕', '葡萄', '香蕉']

若是N = L[:]的情況,因為N只是複製L的元素,修改N的元素不會影響L

L =  ['蛋糕', '蘋果', '香蕉']
N = L[:]
N[1] = '葡萄'
print(L) # ['蛋糕', '蘋果', '香蕉']

最後看個切片有趣的應用吧~

應用: 反轉一個字串

文字充滿著奧妙,不論中、英文,
有些字倒著看也具有意思,
例如:

刷牙<->牙刷
國王<->王國
dog<->god
desserts(甜點) <-> stressed(壓力)
live(生命) <-> evil(邪惡)

你能否利用切片運算符實現字串倒轉的函數呢?

其實很簡單,要倒著取字只要步長設為負數即可,
又因要取整個字串,切片的第一、二個參數都可以省略。
實作如下:

def reverseStr(s):
    return s[::-1]

print(reverseStr("desserts")) # stressed
print(reverseStr("刷牙")) #牙刷

課後練習

為了看小馬文章學習的邦友們,
這邊準備的熱騰騰的課後練習給大家寫寫看

習題: 明日歌星

阿拉拉參加一個唱歌選拔賽,
總共有n個評審(至少有3個),
每個評審在聽完阿拉拉的歌曲後,
會給出1~10分的分數。

在賽制中,
為了更加客觀的評分,
給出最高分與最低分的評審分數會被去除,
僅留下其餘n-2個評審排序後的評分。
舉例來說,
假設n=10,
十位評審的評分為:

scores = [10, 10, 3, 10, 9, 10, 2, 10, 6, 5]

那麼10分與2分各自為最高分與最低分的分數,
因此去除這兩個分數後,
希望返回

[3, 5, 6, 9, 10, 10, 10, 10]

請實作函數judge(scores),

def judge(scores):
    return #這邊輸入你要回傳的值

參數scores是一個長度至少為3的數字列表,
表示n個評審的評分,
請回傳去除最高分與最低分的評審分數後的新列表
(提示: 切片語法 + 某個內建函數)


1
sslinn
iT邦新手 5 級 ‧ 2020-04-10 11:01:15

若是N = L[:]的情況,因為N只是複製L的元素,修改N的元素不會影響L

L =  ['蛋糕', '蘋果', '香蕉']
N = L[:]  <----------這裡
N[1] = '葡萄'
print(L) # ['蛋糕', '蘋果', '香蕉']
心原一馬 iT邦研究生 5 級 ‧ 2020-04-10 11:04:50 檢舉

謝謝糾正,最近一天寫大量文章,感覺常常筆誤,
感謝您的熱心提醒/images/emoticon/emoticon41.gif

1
sslinn
iT邦新手 5 級 ‧ 2020-04-10 11:59:51
def judge(scores):
    newList = scores[:]
    newList.sort()
    return newList[1:-1]

選我最佳解~

心原一馬 iT邦研究生 5 級 ‧ 2020-04-10 12:04:44 檢舉

雖然在此解題沒有點數,不過恭喜解題成功~ /images/emoticon/emoticon42.gif

1
Pondudu
iT邦新手 5 級 ‧ 2020-04-16 13:49:14

小馬哥您好,這是我的答案:

def judge(scores):
    return sorted(scores[:])[1:-1]

另外想請問,打出來的語法是越精簡就代表越好嗎? 如果我打成:

def judge(scores):
    newList = scores[:]
    S = sorted(newList)
    return S[1:-1]

其實就閱讀的部分,我反而覺得第二種比較方便? 但如果整個程式檔很大,是不是盡量越精簡越好?? 但我用time去跑發現第二個竟然比較快太驚訝了!(快約0.008秒) 只是想詢問要往哪個思維去想比較好XD (484想的太複雜了/images/emoticon/emoticon37.gif)

心原一馬 iT邦研究生 5 級 ‧ 2020-04-16 15:30:25 檢舉

哈囉~ 你問的問題很好哦,
程式碼是不是越精簡越好呢?
小馬一開始也認為程式碼應該也是愈精簡愈好,
三行程式碼總是比十行程式碼還要更快讀完

但是「精簡」與「詳盡」畢竟是一體兩面的東西,
程式碼愈長,所能包含的資訊多,
過短的程式碼有可能省略過多東西而不好閱讀,
所以應該是能夠兼顧「可讀性」的精簡語法才是比較好的。

所以如果寫一行會很難讀懂的話,
有時候會寧願寫三行寫仔細一點

所以若是偏好寫

def judge(scores):
    newList = scores[:]
    S = sorted(newList)
    return S[1:-1]

這種的話那也很好,
寫自己容易讀的即可

不過大概小馬看精簡語法看的習慣了,
小馬喜歡這種:

def judge(scores):
    return sorted(scores)[1:-1]

對了,這邊是不用寫sorted(scores[:])
sorted(scores)即可,
原因是sorted()的效果並不會去修改原本陣列的內容,
直接將scores傳入sorted()函數即可

至於程式碼量很大的時候的撰寫技巧,
會希望將各種功能切割成函數包裝好,
以方便去使用它,
而非將所有程式碼展開寫在主程式裡
(不過這部分可能會需要自己寫過幾支較大的程式會比較有感覺xdd)

最後,其實初學不用太在意微小效能的差別啦,
小馬實測覺得可能因為時間極短,
測出來有可能有點小誤差,
方法一、二其實沒有說哪個比較快,
誰快誰慢都有可能

通常寫程式演算法的時候,
或者寫大型程式的時候,
才會比較在意程式速度

祝 學習順利~

Pondudu iT邦新手 5 級 ‧ 2020-04-16 23:37:53 檢舉

了解!謝謝小馬哥^^~

我要留言

立即登入留言