[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就是一列火车,每节车厢都能装一个数据,这样整个火车就装了很多个数据了。
# [插画:类似下图,一列小火车]
![](http://7xtc8l.com1.z0.glb.clouddn.com/20180118115449.png)
使用list表示上面四个技能的代码如下:
```
skills = ['一墨横空', '墨渡迷津', '墨之纵横', '墨下乾坤']
```
# [插图:四个舞剑的小人,类似下图]
![](https://img.kancloud.cn/78/d0/78d0b60ac94498d936af3a3db1d58c4b_400x157.png)
它的语法是使用一对中括号,中间写上多个数据,数据与数据之间用逗号“,”分割,最后一个数据后可以有逗号,也可以没有。
小墨:哦?这样就能避免我说的重复的问题了吗?
墨哥哥: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”,即“硬编码”,意思就是“写死了”。
# [插画:类似下图的对话]
![](http://7xtc8l.com1.z0.glb.clouddn.com/image/python_java/17f531ef4aa4c99ceb7078d24600058b_hd.jpg)
在软件更新迭代的过程中,原本写的很多东西都有可能更改,这时如果采用的是硬编码,如果需要改的地方很多就加大了工作量,同时还有可能因为遗漏一两处没改而产生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使用技能会消耗魔法
* 添加商店系统
* 背包系统
* ……