[Toc]
# 第6章 list
今天是周六,小墨想給《小墨歷險記》中的主人公小墨添加技能,這樣玩家就能使用技能攻擊boss狂風了。不料添加技能過程中卻遇到了問題,他就又來求助墨哥哥了。小墨遇到了什么問題?墨哥哥又是如何解決的呢?一起來看看吧。
時間:周六上午
地點:小墨家
小墨:墨哥哥,今天你還有空嗎?
墨哥哥:有啊,怎么了小墨?
小墨:想問你一個問題,我想在昨天的小墨歷險記第4版基礎上添加小墨的技能攻擊,就定義了3個變量表示小墨的3個技能,然后想玩家輸入1就是普通攻擊,輸入2就是使用第一個技能攻擊,輸入3就是使用第二個技能攻擊,以此類推。可是在寫的過程中發現了個問題,代碼大概如下:
```
skill1 = '亢龍有悔'
skill2 = '飛龍在天'
skill3 = '見龍在田'
while True:
input_ni = int(input("快輸入數字1攻擊他!"))
if input_ni == 1:
# 此處編寫普通攻擊時的代碼:包括boss扣血和boss反擊及玩家扣血
elif input_ni == 2:
# 此處編寫使用第一個技能時的代碼:包括boss扣血和boss反擊及玩家扣血
elif input_ni == 3:
# 此處編寫使用第二個技能時的代碼:包括boss扣血和boss反擊及玩家扣血
elif input_ni == 4:
# 此處編寫使用第三個技能時的代碼:包括boss扣血和boss反擊及玩家扣血
else:
print("請使用數字鍵1攻擊!")
```
墨哥哥:亢龍有悔,飛龍在天,小墨你最近在干嘛呢?
小墨:啊!?你別管了,就說這個程序怎么辦吧。
墨哥哥:你還沒說你程序怎么了呢?
小墨:你看我寫了4個分支,表示輸入1、2、3和4的時候,這些時候都需要編寫boss扣血、boss反擊和玩家扣血的代碼,也就是說類似的代碼我要寫4遍,這也太麻煩了吧?
墨哥哥:你想怎么樣呢?
小墨:博士曾經說過,重復性的勞動要考慮循環,但是這里我不知道該怎么用循環了。
墨哥哥:哦這樣啊。這個地方要想避免重復,你可能需要了解Python中一種新的數據類型:list。
## 6.1 為什么要使用list
墨哥哥:到現在為止,你已經熟悉了變量的用法,如下:
```
name = '張三'
age = 20
```
上面兩句代碼,一個變量就表示一個具體數據,比如age就表示20,在《小墨歷險記》中,如果想使用一個變量表示一個技能,可以這樣:
```
skill = '一墨橫空'
```
如果你有四個技能呢?可以這樣:
```
skill1 = '一墨橫空'
skill2 = '墨渡迷津'
skill3 = '墨之縱橫'
skill4 = '墨下乾坤'
```
這些你都掌握的很好。
小墨:這些都是博士教我的,不過墨哥哥,你這技能……也太能編了吧?
墨哥哥:哈哈,這些都是墨家絕學。回到程序上來,如果你現在定義的不是技能,而是你們班或者你們學校所有同學的名字,難道我們要定義幾十上百個變量嗎?那樣太麻煩了!
為了能一次性的表示很多數據,可以使用Python中的“list”數據類型,“list”中文叫列表,是一種有序的數據集合,如果說普通類型就是一個小貨車,只有一節車廂,只能裝一個數據的話,那么list就是一列火車,每節車廂都能裝一個數據,這樣整個火車就裝了很多個數據了。
# [插畫:類似下圖,一列小火車]

使用list表示上面四個技能的代碼如下:
```
skills = ['一墨橫空', '墨渡迷津', '墨之縱橫', '墨下乾坤']
```
# [插圖:四個舞劍的小人,類似下圖]

它的語法是使用一對中括號,中間寫上多個數據,數據與數據之間用逗號“,”分割,最后一個數據后可以有逗號,也可以沒有。
小墨:哦?這樣就能避免我說的重復的問題了嗎?
墨哥哥:list的不光能用來表示很多數據,最關鍵的是它還能統一處理這些數據:對于list這輛小火車來說,它給自己的每節車廂都編了個號,這個編號稱為列表的“下標”,我們可以通過下標取得任意一節車廂的數據,取出方式是list的名稱加上中括號,中括號中寫上編號,比如skills[1]、skills[3]等,這些數據稱為列表的“元素”。
小墨:哦這樣啊,那如果我想對boss使出第三個技能,只需要通過skills[3]就可以取得第三個技能了對吧。
墨哥哥:不對哦,在使用list時需要注意的是,編號是從**0**開始的。也就是說skills[0]會得到skills這個列表的第一個元素,skills[1]會得到第二個元素,依次類推。你想取第三個技能,使用skills[2]就可以了。
代碼如下:
```
skills = ['一墨橫空', '墨渡迷津', '墨之縱橫', '墨下乾坤']
print(skills[2])
```
輸出為:
```
墨之縱橫
```
小墨:哦原來是這樣,我記住了。
墨哥哥:這就是所謂“一入編程深似海,從此數數從0始”。另外鑒于python程序中list非常非常常見,今天呢我就詳細的給你講一講list的各種用法。
## 6.2 獲取list全部元素
墨哥哥:如果想獲取全部技能,可以這樣:
```
skills = ['一墨橫空', '墨渡迷津', '墨之縱橫', '墨下乾坤']
print(skills[0])
print(skills[1])
print(skills[2])
print(skills[3])
```
輸出為:
```
一墨橫空
墨渡迷津
墨之縱橫
墨下乾坤
```
代碼的第2-5行可以看到,這4行代碼除了下標不同其他都相同,對于相同或相似的代碼,可以考慮使用循環來解決:
```
skills = ['一墨橫空', '墨渡迷津', '墨之縱橫', '墨下乾坤']
for i in range(4):
print(skills[i])
```
墨哥哥:這里的range函數,小墨知道是什么嗎?
小墨:知道,會依次得到0、1、2、3,然后賦值給變量i。
墨哥哥:是的,這樣得到的每個i正好對應skills的下標。
當然了,for循環for x in m中的m本身就可以是list類型,所以我們有更簡單的取出所有元素的方式:
```
skills = ['一墨橫空', '墨渡迷津', '墨之縱橫', '墨下乾坤']
for x in skills:
print(x)
```
從數據類型上說,for i in range(4)取出的每個i都是整數類型的,現在這種方法取出的每個x都是字符串類型的。
不管是哪種類型的list,for循環都可以從中把數據取出來。編程中把某種數據結構中的元素全部訪問一遍(取出來)的過程,稱為**遍歷**。
## 6.3 list的其他用法
墨哥哥:再來說說list的其他用法。很多軟件中都有對數據的管理模塊,比如微信的好友,有添加好友功能、刪除好友功能、禁止好友看我朋友圈功能等,總結一下就是:增加、刪除、修改、查詢等功能,這也是編程中針對數據最常見的操作。
list,作為存儲有若干數據的小火車,也提供了對數據(車廂)的管理操作。簡單來說就是小火車的車廂并不是一成不變的,可能會新增若干節車廂,可能會減少若干節車廂,可能車廂數量沒有變化,但是車廂中裝的東西變化了。
### 6.3.1 增加
墨哥哥:先來說增加功能,使用“list.”調用“append()”方法就完成了車廂的增加了。比如我們添加一個`唯墨獨黑`的技能:
```
skills = ['一墨橫空', '墨渡迷津', '墨之縱橫', '墨下乾坤']
skills.append("唯墨獨黑")
# 直接打印可以看到所有元素
print(skills)
```
輸出:
```
['一墨橫空', '墨渡迷津', '墨之縱橫', '墨下乾坤', '唯墨獨黑']
```
你看,新元素被添加到了列表的末尾。
# [插畫:小火車后面拼上了一節新車廂]
### 6.3.2 插入
墨哥哥:插入也屬于增加,使用“insert()”方法,通過下標指定要插入的位置:
```
skills = ['一墨橫空', '墨渡迷津', '墨之縱橫', '墨下乾坤']
# 下標為1,表示插入到第二個位置上
skills.insert(1, '唯墨獨黑')
print(skills)
```
輸出:
```
['一墨橫空', '唯墨獨黑', '墨渡迷津', '墨之縱橫', '墨下乾坤']
```
這里insert中的1,也是下標,表示將新元素插入到了第**2**個位置上,由于是插入到第二個位置上,此時原第2個位置及2以后所有位置上的元素都需要順勢往后移動一位,這和現實中的想在第一節和第二節車廂間加一節車廂的道理是一樣的。
小墨:好的我記住了,append()會追加到后面,insert()可以選擇插入指定的位置。
# [插畫:小火車中間插入了一節車廂]
### 6.3.3 刪除
墨哥哥:刪除使用“pop()”方法,括號中如果什么都不寫的話,表示刪除最后一個元素:
```
skills = ['一墨橫空', '墨渡迷津', '墨之縱橫', '墨下乾坤']
skills.pop() # 括號中沒有內容,刪除最后一個元素
print(skills)
```
輸出:
```
['一墨橫空', '墨渡迷津', '墨之縱橫']
```
而如果要刪除指定元素,括號中傳入要刪除元素的下標:
```
skills = ['一墨橫空', '墨渡迷津', '墨之縱橫', '墨下乾坤']
skills.pop(2) # 刪除下標為2的元素,也即第3個元素
print(skills)
```
輸出:
```
['一墨橫空', '墨渡迷津', '墨下乾坤']
```
# [插畫:小火車減少一節車廂]
### 6.3.4 修改
墨哥哥:修改是車廂不變但改變了車廂中所裝的東西。直接給某一個下標的元素對應一個新值,就完成了修改的操作。比如要把第一個技能修改為`唯墨獨黑`:
```
skills = ['一墨橫空', '墨渡迷津', '墨之縱橫', '墨下乾坤']
skills[0] = '唯墨獨黑'
print(skills)
```
輸出為:
```
['唯墨獨黑', '墨渡迷津', '墨之縱橫', '墨下乾坤']
```
這些就是常見的list的操作了。
小墨:嗯記住了。我來總結一下:append()用于在list后追加新元素;insert()用于指定下標插入新元素,此時原位置及后面的元素會后移給新元素騰地方;pop()用于刪除元素,可以指定下標也即根據下標刪除,而如果想修改,直接把新的值賦給對應元素,原元素的內容就被覆蓋掉了。是這樣吧墨哥哥?
墨哥哥:總結的不錯,看來你都已經掌握了。上面是list的基礎用法,除了要熟悉這些基礎用法之外,最好能知道一些使用list時的注意事項,這樣編程的時候才能盡量少的出錯。
# [插畫:小火車其中一節車廂拉的東西變了]
## 6.4 注意事項
### 6.4.1 下標問題
墨哥哥:在使用下標取得某一個元素時,除了能使用正下標,還能使用負下標,表示從后往前取,比如skills[-1]表示獲取倒數第一個元素,skills[-2]表示獲取倒數第二個元素,以此類推。
```
skills = ['一墨橫空', '墨渡迷津', '墨之縱橫', '墨下乾坤']
print(skills[-1]) # 獲取倒數第一個元素
print(skills[-2]) # 獲取倒數第二個元素
```
輸出為:
```
墨下乾坤
墨之縱橫
```
注意,正下標是從**0**開始的,負下標是從**-1**開始的。
小墨:負下標還真是貼心呀。
墨哥哥:是的,使用時根據情況選擇正或負下標即可,但是需要注意越界問題。
### 6.4.2 越界問題
墨哥哥:小墨,以上述的skills為例,如果下標超過了最后一個元素的下標,你覺得會怎樣呢?
```
skills = ['一墨橫空', '墨渡迷津', '墨之縱橫', '墨下乾坤']
print(skills[4]) # 傳入4,表示取第5個元素,但是現在一共4個元素
```
小墨:嗯……,那肯定什么都取不到了。
墨哥哥:運行看下結果:
```
Traceback (most recent call last):
File "D:/my_python/list_index.py", line 2, in <module>
print(skills[4]) # 傳入4,表示取第5個元素,但是現在一共4個元素
IndexError: list index out of range
```
會報下標越界“list index out of range”錯誤。同樣的,如果負下標超過了最前面一個元素的負下標,同樣會報這個問題。
所以小墨,在使用list時應避免下標越界問題哦。
小墨:我記住了。
# [插畫:小火車后面跟著虛框框起來的一節車廂]
### 6.4.3 硬編碼問題
墨哥哥:再來看下我們上面說過的通過下標取list中全部元素的代碼:
```
skills = ['一墨橫空', '墨渡迷津', '墨之縱橫', '墨下乾坤']
for i in range(4):
print(skills[i])
```
如果修改了代碼,在skills后面新增加了一個“唯墨獨黑”技能,這時候想要把元素全部遍歷出來,需要把range中的4改為5才行。
那如果忘了改了會怎么樣?
小墨:忘了改了會造成只取出了前4個元素,少取了1個的問題,不過墨哥哥,這個應該不會忘吧?
墨哥哥:現在代碼比少所以不容易忘,如果代碼多了就有可能忘了。比如現在代碼中有20個地方需要將4改為5,可能你改了19處,就忘了1處,代碼就會出問題了。
小墨:哦原來是這樣。
墨哥哥:這種在代碼中將可能會變的量卻寫成了固定不變的值的寫法,稱為“hard code”,即“硬編碼”,意思就是“寫死了”。
# [插畫:類似下圖的對話]

在軟件更新迭代的過程中,原本寫的很多東西都有可能更改,這時如果采用的是硬編碼,如果需要改的地方很多就加大了工作量,同時還有可能因為遺漏一兩處沒改而產生bug。所以在編寫程序的時候,盡量要避免把某些變化的值寫死。
如何才能避免寫死呢?
對于list而言,可以使用len函數獲取列表的長度,替代上面的4。代碼如下:
```
skills = ['一墨橫空', '墨渡迷津', '墨之縱橫', '墨下乾坤']
for i in range(len(skills)):
print(skills[i])
```
這里len(skills)會去動態獲取skills的元素的個數,也稱為列表的長度,然后將結果放到range中替代原先的4,就變成range(len(skills))了。雖然len(skills)的結果還是4,但是這個過程就是動態獲取的了,如果skills中新增了一個元素,這里就會“自動”變成了5,這樣就避免寫死了。
小墨:哦,這個算是一個小技巧,我已經收入囊中了。
墨哥哥:好,關于list的使用及注意事項我就先給你介紹這么多。現在趁熱打鐵,在《小墨歷險記》中加入技能攻擊吧。
小墨:好的,加入技能攻擊:
```
import random
print("歡迎來到墨家村,如今是妖獸的地盤")
print("歷經九死一生,你來到了boss狂風的老巢")
hp_boss = 100
hp_player = 100
print("狂風血量:", hp_boss, ",準備開始戰斗!!")
# 加上普通攻擊,定義5個技能
skills = ['一墨橫空', '墨渡迷津', '墨之縱橫', '墨下乾坤', '普通攻擊']
# 定義5個技能對應的傷害值
skills_hp = [20, 30, 40, 50, 10]
while True:
# 遍歷介紹6個技能
for i in range(1, 6):
print('輸入數字%s釋放第%s個技能:%s' % (i, i, skills[i - 1]))
# 接收用戶輸入的數字
input_ni = int(input("快輸入數字攻擊他!"))
# 如果是1-5
if input_ni == 1 or input_ni == 2 or input_ni == 3 or input_ni == 4 or input_ni == 5:
# 玩家的隨機攻擊傷害值:技能對應的傷害加上1-5的隨機值
attack_player = random.randint(1, 5) + skills_hp[input_ni - 1]
# boss扣血
hp_boss -= attack_player
# 如果boss血量在被攻擊之后小于0了則將血量置為0,防止輸出boss血量為負的情況
if hp_boss < 0:
hp_boss = 0
print("你使用%s擊中了狂風,打出了%s點的傷害,狂風剩余血量%s" % (skills[input_ni - 1], attack_player, hp_boss))
if hp_boss > 0: # 判斷boss是否已死,血量大于0說明還活著,活著就會反擊
# boss的隨機反擊傷害值
attack_boss = random.randint(30, 50)
# 玩家扣血
hp_player -= attack_boss
# 如果玩家血量在被攻擊之后小于0了則將血量置為0,防止輸出血量為負的情況
if hp_player < 0:
hp_player = 0
print("憤怒的狂風發起了反擊,對你造成了%s點傷害,你當前剩余血量%s" % (attack_boss, hp_player))
if hp_player == 0: # 判斷玩家是否已死
print("很遺憾,你未能完成冒險,請休息片刻重新開始。。。")
break
else:
print("小墨,恭喜你,擊敗了狂風!")
break
# 如果用戶輸入的不是1-5,則提醒用戶
else:
print('快輸入數字鍵1-5攻擊狂風!')
```
這樣我的第五版小墨歷險記就做完了。
## 6.5 本章小結
墨哥哥總結:本章主要學習了list這種數據類型的使用,主要是增加、刪除、修改、查詢等功能,并把這些功能最終用在了小墨歷險記的第5版中。小讀者們,你能繼續擴展這個游戲嗎?比如:
* boss添加4個技能、
* 增加魔法量,玩家或boss使用技能會消耗魔法
* 添加商店系統
* 背包系統
* ……