《瘋狂的Python》節選
第5章 序列-列表和元組
序列是程序設計中常用的數據存儲方式,除了我們在前面介紹的字符串(string),Python中提供的另外兩種序列類型就是列表(list)和元組(tuple),相比字符串,列表和元祖能夠存儲的內容更豐富、更靈活多樣。其實,幾乎每一種程序設計語言都提供了序列類型的數據結構,只是名字不同,比如數組。
但是Python所提供的序列類型能夠實現的功能在所有程序設計語言中是最強大的,有時甚至超出了簡單的數據的框架。
5.1《英雄無敵》-迭代開發-構建英雄世界
學習列表之前我們先搬出英雄無敵,看一下在這個小程序中實際我們要解決的問題。經過前面的學習我們已經可以控制程序的流程了,但相信還有很多你覺得蹩腳或無法實現的效果。
現在先放下你要成為一個優秀程式設計師的想法。其實,一個成熟的應用往往並不是程式設計師一個人的功勞,一個項目從創建到落地是需要很多角色配合的結果,比如策劃、產品、經理,暫且不說角色,我們專注的看一下一個程序的誕生過程。
首先,應該沒有人像詩人一樣突然靈感爆發一氣呵成寫出一個程序,即便有,在這之前也一定有過深思熟路的積累才能爆發。通常程序開發的前期需要進行需求分析,比如《英雄無敵》這個遊戲要實現的功能、要呈現的效果、把它們列出來:
《英雄無敵》初步需求:
1. 註冊、登錄、驗證
2. 給角色起個名字,初始化英雄
3. 遊戲的前奏
4. 滿血出場
5. 有地圖
6. 發生隨機事件
7. …………
有了需求,就可以開始琢磨整個程序的大框架了,整個流程的控制,功能的設計,最後最後才是開始敲代碼,這時候其實代碼可能已經在腦子裡是個草稿了。寫出來之後還要經過反覆的測試、調試。不過,即便可以成功運行了,也不要高興得太早,可能需求這時候又變了。什麼?程序寫完了需求又變了?嗯,程式設計師永遠不知道客戶和產品經理會有什麼想法。這也是大家經常開玩笑說程式設計師和產品經理勢不兩立的原因,還好現在不用擔心,因為現在你是身兼所有職位在開發項目。那麼在這個需求當中,列表和元組有什麼作用呢?別急,往下看。
5.2 列表
作為序列類型的列表(list)跟字符串相比,相同是所有關於序列的操作都是通用的。
不同的有兩個方面,一個是字符串中的值只能是字符;在列表中值可以是任何類型。我們稱列表中的值為元素或列表項。另一點,就是列表是可變類型,也就是列表中的元素是可以改變的,這一點在後面會有詳細說明。
明確了相同和差異,就可以套用已知的字符串知識快速掌握列表了。
5.2.1創建列表
Python中創建列表的方法很多,最基本的創建形式就是通過方括號([ ]),其中所有的元素通過逗號分隔開。另外,你也可以通過list()函數創建列表,具體我們來看以下在Python會話中的幾個示例:
>>> aList = []
>>> numList = [1,2,3,4,5]
>>> hero = ['milo',100,'hero']
>>> listInList = [1,2,3,['a','b','c']]
>>> hwList = list('hello world')
>>> hero
['milo', 100, 'hero']
>>> hwList
['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd']
>>> type(aList)
<class 'list'>
>>>
這幾個示例創建了幾個形式各異的列表,我們分別來看一下:
1. aList是一個空列表,也就是裡面沒有數據,不過這樣的空列表我們在程序設計過程中也會經常用到,比如有的時候我們要通過循環或遍歷構建一個新列表,這時我們就可以先建一個空列表,然後再把生成的值添加進來;
2. numList是一個純數字的列表,所有整數用逗號隔開;
3. hero混合了兩種類型的元素;
4. listInList則是一個列表中又包含了另一個列表,這種我們稱之為二元列表;
5. hwList則是通過list()函數講一個字符串轉化成了列表,每一個字符是一個元素,需要注意的是,非集合類型(數字,布爾值)的數據類型不能用list()轉化為列表;
5.2.2列表拆分
列表可以通過賦值的方式進行拆分,
>>> hero = ['milo',100,200]
>>> name, act, hp = hero
>>> name
'milo'
>>> act
100
>>> hp
200
5.3列表的序列化操作
列表有非常豐富的相關操作,這也是列表之所以強大的原因。不過,好在有些是序列通用的,所以比較好記,比如序列相關;有些功能性非常明顯,比如數據的增刪修改。我們先開看一些跟字符串相同的一些操作。
5.3.1索引和切片
讀取列表元素的方式就是變量名加索引(方括號中)因為列表與字符串同屬序列,所以,在列表操作過程中也有索引和切片,而且用法是完全一樣的,只是列表中的元素更豐富了。例如:
>>> hero = ['milo',100,'hero']
>>> hero[0]
'milo'
>>> hero[1]
100
通過索引除了讀取元素,也可以直接對指定索引的元素重新賦值。
二元列表的讀取方式:
>>> listInList = [1,2,3,['a','b','c']]
>>> listInList[3]
['a', 'b', 'c']
>>> listInList[3][1]
'b'
這裡你只要搞清楚,listInList列表中有4各元素,其中第四個元素也是個列表。
需要注意的是列表用方括號表示,讀取索引也用方括號,注意區分,不要搞混了,例如:
這個表達式弄明白,那說明你對列表已經理解了。這個例子經列表的創建和索引操作放在了一起,我們要從左向右閱讀表達式,左邊創建列表,右邊是這個列表的索引。
最後就是需要注意索引越界的問題:
>>> [1,2,3,4,5][5]
Traceback (most recent call last):
File "<pyshell#16>", line 1, in <module>
[1,2,3,4,5][5]
IndexError: list index out of range
明確了索引的運用,接下來就是切片了,具體參照字符串中的切片操作,我們直接看例子吧,:
>>> hwList = list('hello world')
>>> numList = [1,2,3,4,5]
>>> hwList
['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd']
>>> hwList[::2]
['h', 'l', 'o', 'w', 'r', 'd']
5.3.2運算符及函數
字符串的重複(*)、拼接(+)、in和not in在列表上的使用效果是一樣的,我們直接看例子吧:
>>> aList = [1,2,3]
>>> bList = ['a','b','c']
>>> aList + bList
[1, 2, 3, 'a', 'b', 'c']
>>> aList * 3
[1, 2, 3, 1, 2, 3, 1, 2, 3]
>>> 3 in aList
True
>>> 'd' not in bList
True
另外,我們也可以用>、<、= =、<=、>=和!= 比較兩個列表,不過,因為列表元素類型比較多樣,應使用同一類型的元素(全數字或全字符串)避免不同類型的比較:
>>> [1,2,3,4] < [1,2,3,4,5]
True
>>> [1,2,3,4] < [1,2,3,'a']
Traceback (most recent call last):
File "<pyshell#26>", line 1, in <module>
[1,2,3,4] < [1,2,3,'a']
TypeError: '<' not supported between instances of 'int' and 'str'
>>>
還有一些關於序列的通用函數是可以用於字符串、列表和元組的:
1. len()用於獲取序列的元素個數:
>>> len([1,2,3,4,5])
5
>>> len("abcde")
5
2. max()和min()返回序列中元素最大值和最小值:
>>> max([1,4,6,8,5,3,2,9])
9
>>> min([1,4,6,8,5,3,2,9])
1
>>> max('12345')
'5'
>>> min('12345')
'1'
3. sum()對數值型列表元素求和,非數值則報錯:
>>> sum([1,2,3,4,5])
15
>>> sum([1,2,3,4,5,'6'])
Traceback (most recent call last):
File "<pyshell#38>", line 1, in <module>
sum([1,2,3,4,5,'6'])
TypeError: unsupported operand type(s) for +: 'int' and 'str'
4. sorted()對序列進行排序,reversed()可以直接將序列倒序排列:
>>> l = [1,4,7,6,2,9]
>>> sorted(l)
[1, 2, 4, 6, 7, 9]
>>> reversed(l)
<list_reverseiterator object at 0x060D7B10>
>>> next(reversed(l))
9
這裡要注意,reversed返回的是一個迭代器,需要遍歷或調用next()。
5.3.3遍歷
在for循環中我們就已經介紹過通過range()生成一個列表迭代器的方法,現在有了列表,當然可以通過for直接遍歷了,還有上個例子中的reversed()也可以:
>>> for i in ['a', 'b', 'c']:
print(i)
a
b
c
>>> for i in reversed(l):
print(i)
9
2
6
7
4
1
>>>
5.4列表的操作
我們說列表時總會說它是可變的,跟字符串比較,其實就是指,你不能把字符串指定索引位置的字符變成其它字符。而列表就完全可以修改,甚至增加和刪除元素,用起來就像個資料庫一樣,而且我們也確實經常會把列表當做一個臨時的資料庫來用,說臨時是因為,列表的增刪改查都只是在你的一個程序中,程序運行結束後並不會保存下來,不過這個也不是問題,後面會介紹其他技術解決這個問題。
5.4.1可變的列表
列表的可變性,非常有用,我們可以僅僅通過索引或切片來替換列表的元素甚至來個大換血,正因為這樣,更要避免無意的操作帶來失誤,所以我們來看些具體的一些改變列表的方式:
>>> role = ['milo',100,200,'hero']
>>> role[0]
'milo'
>>> role[0] = 'zouqixian' #改變第一個元素
>>> role
['zouqixian', 100, 200, 'hero']
>>> role[-1] = 'monster' #改變最後一個元素
>>> role
['zouqixian', 100, 200, 'monster']
>>> role[1:3] = [2000] #第二到第三個元素替換為一個元素
>>> role
['zouqixian', 2000, 'monster']
>>> role[:] = [1,2,3,4,5,6] #遍歷列表替換為全新的元素
>>> role
[1, 2, 3, 4, 5, 6]
>>> role[:] = "hello world!" #將列表指定元素替換為字符串字符
>>> role
['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!']
>>> role[:] = 12345 #上個操作僅限可迭代數據,整數不可迭代
Traceback (most recent call last):
File "<pyshell#75>", line 1, in <module>
role[:] = 12345
TypeError: can only assign an iterable
在這個例子中所有的操作都是通過指定列表索引進行賦值實現的,這裡的每個操作都對列表的值進行了修改,也就是每次列表標籤(變量名)所對應數據空間的內部數據。在這個過程中因為賦值語句不會有返回值,所以每次我們都通過變量名來查看結果。我們可以改變一個元素,也可以改變所有的元素,唯一需要注意的是被添加的元素必須是一個集合類型,集合中的每個元素作為元素單獨添加到列表中。
5.4.2列表的方法
針對列表的操作其實得益於列表對象的方法,就像字符串類型擁有大量的方法一樣。列表的所有方法可以通過help(list)看到。我們按實現的效果來看一下:
1. 檢索元素
我們可以通過索引就可以獲得列表的元素,通過列表的index(x)方法可以返回x元素的索引,如果元素不存在則報錯。
count(x)可以返回列表中x出現的次數,x是一個元素。
>>> role = ['milo',100,200,'hero']
>>> role.index('hero')
3
>>> role.count(0) #0這個數字出現了4個,但是沒有這個單獨的元素
0
>>> role.count(100) #100出現了1次
1
>>>
2. 增加元素
append()方法可以向列表尾部添加一個新的元素:
>>> role.append('level2')
>>> role
['milo', 100, 200, 'hero', 'level2']
extend()方法可以將另一個列表的元素添加到當前列表,這個跟通過—+號合併兩個列表是有區別的,後面單獨要說一下:
>>> bag = ['AK47','knife',100]
>>> role.extend(bag)
>>> role
['milo', 100, 200, 'hero', 'level2', 'AK47', 'knife',100]
insert()方法可以根據索引將一個元素插入到列表任何位置,這個方法需要兩個參數,第一個是位置也就是索引,第二個是需要插入的元素:
>>> role
['generral', 'milo', 100, 200, 'hero', 'level2', 'AK47', 'knife',100]
3. 刪除元素
remove()方法可以刪除一個指定值的元素,如果有多個則從左至右:
>>> role.remove(100)
>>> role
['generral', 'milo', 200, 'hero', 'level2', 'AK47', 'knife', 100]
>>> role.remove(100)
>>> role
['generral', 'milo', 200, 'hero', 'level2', 'AK47', 'knife']
如果想刪除(彈出)指定位置的元素就可以pop(),彈出的意思指刪除的同時,這個值會返回給調用者:
>>> role
['generral', 'milo', 200, 'hero', 'level2', 'AK47', 'knife']
>>> role.pop(-2)
'AK47'
>>> bag = role.pop(-1)
>>> role
['generral', 'milo', 200, 'hero', 'level2']
>>> bag
'knife'
如果你不需要返回值又想根據索引刪除,可以使用Python的del語句,del語句和切片結合就可以刪除多個元素了:
>>> del role[0]
>>> role
['milo', 200, 'hero', 'level2']
>>> del role[1:]
>>> role
['milo']
5.4.3字符串和列表
我們在字符串的章節有介紹過字符串方法split()可以把字符串按指定參數(默認是空白)分隔並返回一個列表。以及通過字符串方法join()將列表拼接成字符串。這裡不重複說了,可以參考字符串章節相關內容。
5.5列表推導式和生成器表達式
我們可以先來看一個過濾(filter)列表的例子。
之前我們留的作業,已知10以內3,5的倍數為3、5、6、9,和是23,求1000以內3,5倍數的和,這個問題的解決方法很多,我採取下面這個辦法:
>>> numList = []
>>> for i in range(1,10):
if i%3 == 0 or i%5 == 0:
numList.append(i)
>>> numList
[3, 5, 6, 9]
>>> sum(numList)
23
5.5.1列表推導式
列表推導式(list comprehensions)是Python中很強大的、很受歡迎的特性,具有語言簡潔,速度快等優點。具體的作用是通過一個序列生成一個新的列表。什麼是通過一個序列生成一個新的列表?前面的兩個例子都是。
列表推導式的語法為:
[表達式 for 變量 in 列表]
[表達式 for 變量 in 列表 if 條件]
表達式:列表生成元素表達式,可以是有返回值的函數。
for 變量 in 列表:迭代列表將元素傳入表達式中如果有if則先交給if進行過濾。
if 條件:根據條件過濾哪些值可以。
基本的用法比如:
>>> lst = [2,6,3,5,9]
>>> [x**2 for x in lst] #求所有元素的平方
[4, 36, 9, 25, 81]
>>> [x for x in lst if x%2 == 0] #過濾出偶數
[2, 6]
由此,3,5倍數的例子用列表推導式只要一行代碼就可以了:
>>> [i for i in range(1,10) if i%3 ==0 or i%5 ==0]
[3, 5, 6, 9]
>>> sum([i for i in range(1,1000) if i%3 ==0 or i%5 ==0])
233168
5.5.2生成器表達式
生成器(generator)是一種特殊的迭代器,它的工作方式是每次處理一個對象,而不是一口氣處理和構造整個數據結構,這樣做的潛在優點是可以節省大量的內存。
生成器表達式(generator expression)並不真正創建列表, 而是返回一個生成器,生成器表達式語法結構和列表表達式是一樣的,區別在於表達式使用()括起來,而不是[]。上面的例子可以直接變成生成器表達式。
>>> lst = [2,6,3,5,9]
>>> (x**2 for x in lst)
<generator object <genexpr> at 0x061017E0>
這時我們就看到返回值不是列表了,可以採取迭代的方式獲取生成器的值:
>>> lst = [2,6,3,5,9]
>>> g = (x**2 for x in lst)
>>> type(g)
<class 'generator'>
>>> next(g)
4
3,5倍數用生成器表達式就變成了:
>>> sum((i for i in range(1,1000) if i%3 ==0 or i%5 ==0))
233168
看到這可能會有人問只是換了個符號,意義是什麼?這個就要回到迭代器上去了,最大的優點就是可以節省大量的內存了。
5.5.3一點建議
雖然列表推導式和生成器表達式很方便,但並不是什麼時候都可以去用,幾個建議。
1. 當只是執行一個循環就可以的時候儘量使用循環而不是列表解析,這樣更符合Python提倡的直觀性。因為列表推導式有的時候並不易讀。
2. 當有內建的操作或者類型能夠以更直接的方式實現的,不要使用列表解析。例如複製一個列表時,使用:L1=list(L)即可,不必使用:L1=[x for x in L]。
3. 如果需要對每個元素都調用並且返回結果時,應使用L1=map(f,L), 而不是 L1=[f(x) for x in L] ,map這樣的函數還有很多,我們放在函數的章節介紹。
5.6深拷貝淺拷貝
我們一直在說列表是可變的,有時我們會希望對列表做一個備份,所以,在涉及到拷貝的時候會有一些有意思或者讓新手頭疼的問題,在這,我通過一些非常簡單直觀的例子來說明一下。下面跟住我的思路過一遍應該就能明白了:
5.6.1賦值
首先看一下字符串,來作為對比:
s1 = "hello world"
>>> s2 = s1
>>> s2
'hello world'
>>> s1 = ''
>>> s1
''
我定義了字符串s1,然後s2=s1,相當於s1的數據增加了一個名字,所以通過s2訪問到的數據就是s1對應的數據,最後給s1賦值為空字符串,此時的s2的值是什麼?
這裡新手會犯一個錯誤,可能會認為s2會隨著s1的改變而改變,但實際s2的值並沒有改變,其實原因在介紹變量的時候就有說過,s1和s2都是數據的標籤而已,當執行s1=『』時,實際上相當於將s1這個標籤移動到了數據『』上,而s2並沒有移動,數據當然不會改變。
如果你並沒有搞錯,想到了正確答案,很好。那你可能會問,這有什麼意義?接下來就是關於列表的可變特性,我們看下面的例子:
>>> l1 = ['hello', 'world']
>>> l2 = l1
>>> l2
['hello', 'world']
>>> del l1[:]
>>> l1
[]
跟上面的例子類似,只是字符串變成了列表,並且刪除l1所有元素,l2的值是什麼?
想到了嗎?l2的值也清空了,別急,再看一步:
>>> l1 = ['hello', 'world']
>>> l1
['hello', 'world']
l1再賦值,l2會怎樣,同樣獲得值嗎?
依然是空。
什麼原因呢?首先明確一點,在Python中,s1 = s2和l1 = l2一樣的,都是給對象添加新的名字。但是為什麼列表的值被改變的時候,另一個名字所對應的值也變了呢?這是因為創建列表時數據的存儲方式,我們看一下l1 = l2之後的情況:
圖 引用
從這個結構表中可以看出l1和l2使用的是同一個存儲空間,所以這時我們通過任何一個名字對值進行改變,另一方所訪問的空間不變,獲取的值當然不變。而當我們第二次執行l1 = [『hello』, 』world』]時,實際上是將l1這個名字移動到一個新的列表對象上了:
圖 重新賦值
總結以上, 就是關於Python中賦值操作的效果:
1. 賦值是將一個對象的地址賦值給一個變量,讓變量指向該地址( 舊瓶裝舊酒 )。
2. 修改不可變對象(str、tuple)需要開闢新的空間。
3. 修改可變對象(list等)不需要開闢新的空間。
5.6.2淺拷貝
Python提供了一個copy模塊,其中的copy()方法可以實現淺拷貝,淺拷貝僅僅複製容器中元素的地址,效果如下:
>>>import copy
>>> a = ['hello',[1,2,3]]
>>> b = copy.copy(a)
>>> [id(x) for x in a]
[65592207, 5623045]
>>> [id(x) for x in b]
[65592207, 5623045]
>>> a[0] = 'world'
>>> a[1].append(4)
>>> print(a)
['world', [1, 2, 3, 4]]
>>> print(b)
['hello', [1, 2, 3, 4]]
這個例子中,經過淺拷貝之後的副本,再做修改之前元素的地址是相同的,此時淺拷貝只是將源列表中元素的地址複製了一份,修改a列表內的不可變元素字符串時移動了地址,b不跟著改變,而可變的列表元素則在內部增加了值,a和b的這個列表元素地址相同,所以同時變化。
總結以上,淺拷貝是在另一塊地址中創建一個新的變量或容器,但是容器內的元素的地址均是源對象的元素的地址的拷貝。也就是說新的容器中指向了舊的元素。
最後,除了copy模塊,我們還可以通過切片操作、工廠函數list()、對象的copy()方法等方法實現淺拷貝:
>>> a = [1,2,3,4]
>>> b = a[:]
>>> c = list(a)
>>> d = a.copy()
5.6.3深拷貝
深拷貝的方法是copy.deepcopy(),完全拷貝一個副本,容器內部元素地址都不一樣:
>>> from copy import deepcopy
>>> a=['hello',[1,2,3]]
>>> b=deepcopy(a)
>>> [id(x) for x in a] [46952708, 66053004]
>>> [id(x) for x in b] [48657389, 66028736]
>>> a[0]='world'
>>> a[1].append(4)
>>> print(a)
['world', [1, 2, 3, 4]]
>>> print(b)
['hello', [1, 2, 3]]
這個理中可以很明顯的看到,深拷貝後的對象地址和元素的地址都不同,可以看作是值相同的的完全拷貝的副本,原對象怎麼修改都不會影響深拷貝。
總結以上,我們用一個圖結束:
圖 淺拷貝和深拷貝
5.7元組
元組可以看作是不可變的列表。元組幾乎具備了列表所有的特徵,不同的就是元組一旦創建就不可改變,不能更改元素,也不能增減元素。
因為元組跟列表的相似,所以,基本上你可以嘗試所有的列表操作,除了更改元組元素,下面我們從區別於列表的一些特性了解一下元組。
5.7.1創建元組
元組實際是用逗號分隔的一列值,這個值也可以是任何類型的對象,創建元組是可以不用括號,但通常我們會用()將所有元素括起來,區別於列表的[]:
>>> t1 = (1, 2, 3, 'a', 'b', 'c')
>>> t2 = 1, 2, 3, 'a', 'b', 'c'
>>> t1
(1, 2, 3, 'a', 'b', 'c')
>>> t2
(1, 2, 3, 'a', 'b', 'c')
這裡需要注意一點,很多時候介紹原則的時候會有一個誤區,說元組的創建符號是(),包括我平時為了避免做過多解釋也這樣說。那就是,實際上真正創建元組的運算符是逗號,Python中圓括號大多數情況下都表示分組,圓括號加上逗號才成為元組創建的一部分,比如創建一個只有一個元素元組,只有括號沒有逗號的話跟元組一點關係也沒有:
>>> t3 = 1,
>>> t3
(1,)
>>> t4 = 1
>>> t4
1
>>> t5 = (1,)
>>> t5
(1,)
>>> t6 = (1)
>>> t6
1
>>> type(t3)
<class 'tuple'>
>>> type(t4)
<class 'int'>
>>> type(t5)
<class 'tuple'>
>>> type(t6)
<class 'int'>
>>>
還有一種特殊的元組就是空元組:
>>> t7 = ()
>>> t8 = tuple()
>>> t7
()
>>> t8
()
tuple()的另一個作用就是將其他序列轉為元組:
>>> t9 = tuple("hello")
>>> t9
('h', 'e', 'l', 'l', 'o')
>>>
5.7.2元組賦值
元組除了可以可以把一組數據賦值給一個變量,也可以進行拆分賦值,比如有一個比較經典的算法問題。有紅墨水和藍墨水各一杯,問怎樣將杯中墨水交換?解決辦法就是再找一個空杯做中轉,用傳統的賦值方式表示就是:
>>> red = "red water"
>>> blue = "blue water"
>>> temp = red
>>> red = blue
>>> blue = temp
這個辦法在Python中就顯得不那麼簡潔優雅了,Python中我們這樣:
>>> red, blue = blue, red
這種等號左右都是元組的方式就是其實就是元組拆分,這種賦值方式需要注意的就是左邊變量數跟右面元組的元素數要相等。
5.7.3列表和元組
列表和元組是可以相互轉換的,各自都提供了轉換函數list()和tuple()。除了相互轉換之外,還有一種情況會把他們拉到一起。
Python提供了一個內置函數zip(),作用是接收多個序列,每個序列取一個值放到一個元組裡,Python2中所有的元組組成一個列表,不過在Python3中不能直接看到這個列表,zip返回一個迭代器,需要迭代才能看到裡面的值。如果序列長度不同則以短的為準:
>>> x = 'xyz'
>>> l = [1,2,3,4]
>>> zip(x, l)
<zip object at 0x05450B48>
>>> for i in zip(x,l):
print i
('x', 1)
('y', 2)
('z', 3)
迭代時可以利用元組賦值方式直接拆分元組:
>>> for v, k in zip(x,l):
print(k, "===>", v)
1 ===> x
2 ===> y
3 ===> z
這種組合用來解決某些問題還是很有用的,比如判斷兩個序列當中是否在同一位置含有相同元素:
>>> for x, y in zip(l1, l2):
if x == y:
print(x)
5.7.4什麼時候用元組
元組與同為序列的字符串和列表,應該根據什麼決定使用元組呢?
首先是字符串,相比其他序列,字符串的限制比較多,元素只能是字符而且不可變。
列表是最靈活的,相比元組,用到的地方更多一些,所以我們用了較大篇幅來介紹。元組作為跟列表相似的數據類型,重點就在於它的不可變性,這種不可變性提供了一種具有完整性和持久性的數據結構。因此,可以為需要固定數據的地方提供不可變對象。比如在後面的字典類型的鍵就是不可變的,這時就只能用元組而不能用列表。
5.8《英雄無敵》需求落地
現在我們可以研究下開篇的英雄無敵的一些需求適合用什麼方式來實現了
1.開局玩家角色起名字:input
2.列表初始化英雄屬性:[名字, 血值, 攻擊力, 防禦力]
3.判斷名字是否為空,默認為「玩家一」:if語句
4.判斷英雄行動方向:if
5.設計地圖九宮格:列表或元組
6.優化數據結構
將字符串的英雄無敵做一下升級就大概變成這樣:
'''
Heroes beta-0.2
milo str worldMap if while
'''
welcome = '-*-welcome to Heroes world!-*-'
mapmsg = '#######'
mapins = " -*- the world is like this -*- %s the '*' is you " % (mapmsg,)
worldMap = ['#','#','#','#','#','#','#']
instruction = '''
contrl your hero:| 'a'for left | 'd' for right |'''
print(welcome)
name = input('input your name:')
hp = 100
if not name:
name = 'player01'
usermsg = {'name':name,'hp':hp}
print("HI!", usermsg['name'],'You Hp is :',usermsg['hp'])
print(mapins,instruction)
point = 0
while 1:
worldMap[point] = "*"
print('you are here',"".join(worldMap))
userinput = input('go or quit:')
if userinput == 'd' and point < 6:
worldMap[point] = '#'
point +=1
elif userinput == 'a' and point > 0:
worldMap[point] = '#'
point -=1
elif userinput == 'quit':
print("goodbey!!")
break
else:
print(instruction)
當然,這只是一個思路而已,你設計的遊戲是什麼樣子?自己實現吧!應該比我這個有意思吧。