Python 程式設計入門 - 03

Pyladies Taiwan

Speaker : Mars

2017/10/15

Roadmap

  • 前情提要:可變與不可變、序列型別
  • 迴圈與迭代
  • 序列切片
  • 再談串列存取
  • 串列增刪
  • 應用情境:密碼學
  • 補充知識

前情提要:可變與不可變、序列型別

可變與不可變

intfloatstrtuple是不可變(immutable)物件,在建立之後就不能更改他的值,
list是可變(mutable)物件。

In [1]:
l = ["a",2016,5566,"PyLadies"]
s = "PyLadies"
l[1] = 2017
s[1] = "a"
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-1-a4ae7bfd1b9d> in <module>()
      2 s = "PyLadies"
      3 l[1] = 2017
----> 4 s[1] = "a"

TypeError: 'str' object does not support item assignment
In [2]:
a = 3
b = 3
c = a
print(a is b,a is c) # is 判斷是否指向同一個物件
print(id(a),id(b),id(c))
a += 2  # a = 5
b = 4   
print(a is b,a is c)
print(id(a),id(b),id(c))
True True
10105888 10105888 10105888
False False
10105952 10105920 10105888

不可變物件,進行運算、或是重新指向,都是直接指向新的物件。

In [3]:
l1 = ["a",2016,5566,"PyLadies"]
l2 = l1
print(l1 is l2,id(l1),id(l2))
True 140186906439752 140186906439752
In [4]:
l1 += ["Hi"]
print(l1 is l2,id(l1),id(l2))
print(l1)
print(l2)
True 140186906439752 140186906439752
['a', 2016, 5566, 'PyLadies', 'Hi']
['a', 2016, 5566, 'PyLadies', 'Hi']
In [5]:
l2[2] = 2017
print(l1 is l2,id(l1),id(l2))
print(l1)
print(l2)
True 140186906439752 140186906439752
['a', 2016, 2017, 'PyLadies', 'Hi']
['a', 2016, 2017, 'PyLadies', 'Hi']

!注意

  • 可變物件不管怎麼修改,位置還是不會變的
  • 如果有兩個名稱指向同一個可變物件,物件一修改,
    兩個名稱取到的都會是同樣一個修改之後的物件

序列型別

常見序列型別的有字串(string)、串列(list)、元組(tuple)

存取串列(list)元素

  • my_list[i]:取得索引(index)在i的元素
  • 索引可以是負數,如果是索引為-i,會被當作拿取索引為「串列長度-i」的元素

!注意

  • Python 是從0開始數
In [6]:
# 索引值     0 , 1  ,  2 ,    3     ,  4 ,  5   
my_list = ["a",2016,5566,"PyLadies",2016,2016.0] # 這是一個長度為6的串列
print ("The 4th  element",my_list[3])
print ("The last element",my_list[-1])           # 等同於拿取索引=6-1=5的元素
print ("The second-last element",my_list[-2])    # 等同於拿取索引=6-2=4的元素
The 4th  element PyLadies
The last element 2016.0
The second-last element 2016
In [7]:
my_list = ["a",2016,5566,"PyLadies",2016,2016.0]
b = my_list[1]
my_list[2] = 2017
print(b)
print(my_list)
2016
['a', 2016, 2017, 'PyLadies', 2016, 2016.0]

「字串」跟「串列」很像

  • 字串的每個元素都是一個字元(字母或符號)
  • 用同樣的方式存取元素
In [8]:
my_string = "PyLadies Taiwan"
print ("The 1st  element of my_string = ",my_string[0])
print ("The 8th  element of my_string = ",my_string[7])
print ("The last element of my_string = ",my_string[-1]) 
The 1st  element of my_string =  P
The 8th  element of my_string =  s
The last element of my_string =  n

迴圈與迭代

迭代是的概念是「一個接著一個處理」,
需要有供給可迭代的物件的提供者,讓接收方一個個處理。

序列可以用索引值標示元素的概念,就是很好者提供者,
而處理這些迭代物件的接收方,在Python裡最常用的就是for迴圈!

可迭代者 Iterable、迭代器 Iterator

  • 可迭代者的__iter__()方法,或是用內建函式iter(),可得到回傳的迭代器
  • 迭代器的__next__()方法,或用內建函式next(),每次呼叫會回傳下一個元素
In [9]:
l = [1,4,7,9]
itb=iter(l)
print(next(itb)) # 第1次迭代 1
print(next(itb)) # 第2次迭代 4
print(next(itb)) # 第3次迭代 7
print(next(itb)) # 第4次迭代 9
print(next(itb)) # 第5次迭代 已經沒有元素了
1
4
7
9
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-9-c1ef6164ef53> in <module>()
      5 print(next(itb)) # 第3次迭代 7
      6 print(next(itb)) # 第4次迭代 9
----> 7 print(next(itb)) # 第5次迭代 已經沒有元素了

StopIteration: 
for 命名物件 in 可迭代者:  
    重複要做的事
  • 命名物件指向每次從可迭代者拿出的元素
  • 重複要做的事是在迴圈裡面,所以要記得冒號、縮排
  • 迴圈會取出所有存在可迭代者中的元素。
for i in [1,4,7,9]:
    print (i)

等同於

i=1
print(i)
i=4
print(i)
i=7
print(i)
i=9
print(i)

for最方便的地方就是會自動拿取可迭代者下一個元素,直到無法拿取為止。
原理:會將可迭代者轉換成迭代器,然後用next方法,呼叫下一個元素。

想像我有一個紀錄員工年資的串列,
薪水是依照年資*100+22000 的公式去計算

In [10]:
l = [5.5, 9, 10]
print("員工1的薪水",l[0]*100+22000)
print("員工2的薪水",l[1]*100+22000)
print("員工3的薪水",l[2]*100+22000)
員工1的薪水 22550.0
員工2的薪水 22900
員工3的薪水 23000

3個員工,我就要寫3行告訴我各個員工的薪水,那如果有100個員工...
如果有一個紀錄串列各個索引值0~n的資料型別,就會很方便

range(start,end,stride)

  • range也是一種序列型別,會回傳一系列能迭代的物件,是可迭代者
  • start 是 inclusive,預設是0開始
  • end 是 exclusive,必填值
  • stride 是跳幾次切割,預設是1
    • stride 為負值則由尾端向前端取,需讓start>end

注意!

  • start > end ,則等同於空
  • stride 不得為0
In [11]:
print('range(5)     ',list(range(5)))     # 只有一個代表end
print('range(2,5)   ',list(range(2,5)))
print('range(2,5,2) ',list(range(2,5,2)))
print('range(5,0)   ',list(range(5,0)))    
print('range(5,0,-1)',list(range(5,0,-1))) # 5-1有<0嗎?
print('range(0,5,-1)',list(range(0,5,-1))) # 0-1有<5嗎?
range(5)      [0, 1, 2, 3, 4]
range(2,5)    [2, 3, 4]
range(2,5,2)  [2, 4]
range(5,0)    []
range(5,0,-1) [5, 4, 3, 2, 1]
range(0,5,-1) []
In [12]:
print(list(range(3)))
for i in range(3):
    print(i)
[0, 1, 2]
0
1
2

配合迴圈,這樣可分別拿到0~2的數值,可以當成串列索引

In [13]:
# 土法煉鋼,一個個員工慢慢列
l = [5.5, 9, 10]
print("員工1的薪水", l[0]*100+22000)
print("員工2的薪水", l[1]*100+22000)
print("員工3的薪水", l[2]*100+22000)
# ...
員工1的薪水 22550.0
員工2的薪水 22900
員工3的薪水 23000
In [14]:
# 使用迴圈
l = [5.5, 9, 10]
for i in range(3):
    print(i, "員工{}的薪水".format(i+1), l[i]*100+22000)
0 員工1的薪水 22550.0
1 員工2的薪水 22900
2 員工3的薪水 23000

串列也是一個可迭代者,
如果要取所有串列的元素,除了用索引拿取 range(0,len(串列))
也可以直接用for迴圈處理:

In [15]:
l = [5.5, 9, 10]
for i in l:
    print(i)
5.5
9
10
In [16]:
l = [5.5, 9, 10]
for i in l:
    print(i*100+22000)
22550.0
22900
23000
In [17]:
# 對比 range() 寫法
l = [5.5, 9, 10]
for i in range(3):
    print(l[i]*100+22000)
22550.0
22900
23000

想要印出第幾位員工,
因為直接用串列當成可迭代者,需要紀錄現在是第幾個索引

In [18]:
l = [5.5, 9, 10]
count=0 # 紀錄現在的索引
for i in l:
    print("員工{}的薪水".format(count+1), i*100+22000)
    count = count+1 # count+=1 
員工1的薪水 22550.0
員工2的薪水 22900
員工3的薪水 23000
In [19]:
# 對比 range() 寫法
l = [5.5, 9, 10]
for i in range(3):
    print("員工{}的薪水".format(i+1), l[i]*100+22000)
員工1的薪水 22550.0
員工2的薪水 22900
員工3的薪水 23000

range()更彈性的寫法

range(數值)中,數值最好保持彈性,
如果寫死,一但串列增加元素,就得記得改值,很是不方便!

In [20]:
l = [5.5, 9, 10]
for i in range(3):
    print("員工{}的薪水".format(i+1), l[i]*100+22000)
員工1的薪水 22550.0
員工2的薪水 22900
員工3的薪水 23000
In [21]:
l = [5.5, 9, 10, 2.3]
for i in range(4):
    print("員工{}的薪水".format(i+1), l[i]*100+22000)
員工1的薪水 22550.0
員工2的薪水 22900
員工3的薪水 23000
員工4的薪水 22230.0
In [22]:
l = [5.5, 9, 10, 2.3]
for i in range(len(l)): # 串列有多長就用多長
    print("員工{}的薪水".format(i+1), l[i]*100+22000)
員工1的薪水 22550.0
員工2的薪水 22900
員工3的薪水 23000
員工4的薪水 22230.0

[練習] 調分公式:開根號,再乘10

有一列學生的成績l = [32,56,58,62,79,82,98]
我希望將學生調分:

學生1 56.568542494923804
學生2 74.83314773547883
學生3 76.15773105863909
學生4 78.74007874011811
學生5 88.88194417315589
學生6 90.55385138137417
學生7 98.99494936611666

Hint:

In [23]:
import math
print(math.sqrt(9))
print(math.sqrt(10))
3.0
3.1622776601683795
  • 使用range()
    l = [32,56,58,62,79,82,98]
    import math
    for i in range(__):
      print("學生{}".format(__),math.sqrt(___)*10)
    
  • 使用串列,配合紀錄現在索引位置的物件
    l = [32,56,58,62,79,82,98]
    import math
    count = 0
    for i in l:
      print("學生{}".format(__),math.sqrt(___)*10)
      count = _____
    

[練習]

延續上題,
我想計算條分之後,班上的總平均:
調分後的加總/學生人數

Hint:

跟剛才紀錄索引位置的物件一樣,先設定初始值,
然後每次迴圈的時候就加上條分後的成績。

  • 使用range()
    l = [32,56,58,62,79,82,98]
    import math
    total_score = 0
    for i in range(__):
      print("學生{}".format(__),math.sqrt(___)*10)
      total_score += _____
    print("總平均",total_score/____)
    
  • 使用串列,配合紀錄現在索引位置的物件
    l = [32,56,58,62,79,82,98]
    import math
    total_score = 0
    count = 0
    for i in l:
      print("學生{}".format(__),math.sqrt(___)*10)
      count = _____
      total_score += _____
    print("總平均",total_score/____)
    

[練習]

還記得sum(串列)這個內建函式嗎?
他可以針對全數字序列做加總,但無法處理字串。
我們可以來寫一個,不管串列內容是數字或是字串,都以字串方式相加。

l = [1,"Hi",2.5,"Y",3,4]
希望得到的結果是1Hi2.5Y34

Hint:

  • 跟前面的total_score一樣,有一個紀錄累加的地方
  • 數值不能與字串相加,要把數值轉成字串會用到型別轉換
l = [1,"Hi",2.5,"Y",3,4]
s = ""
for __ __ _____:
    s = _____
print(s)

[練習]

接續剛才的練習,
l = [1,"Hi",2.5,"Y",3,4]

  • 如果只想取得奇數(1,3,5...)索引的元素相加呢?
    • 結果會是HiY4
  • 如果想倒轉相加結果呢?
    • 結果會是43Y2.5Hi1

還記得之前算文章某單字出現幾次嗎?
配合迴圈就可以把每個單字拿出來分別算:

In [24]:
article = """Bubble tea represents the "QQ" food texture that Taiwanese love. 
The phrase refers to something that is especially chewy, like the tapioca balls that form the 'bubbles' in bubble tea. 
It's said this unusual drink was invented out of boredom. 
"""
word_list = article.split(" ")
print(word_list[0],word_list.count(word_list[0]))
print(word_list[1],word_list.count(word_list[1]))
print(word_list[2],word_list.count(word_list[2]))
# .....
Bubble 1
tea 1
represents 1
In [25]:
#    ↱ 這裡可以隨意命名(前面都用i)
#   |         ↱ 可迭代者,這裡是切成單字的串列
for word in word_list:
    print(word,word_list.count(word))
Bubble 1
tea 1
represents 1
the 3
"QQ" 1
food 1
texture 1
that 3
Taiwanese 1
love. 1

The 1
phrase 1
refers 1
to 1
something 1
that 3
is 1
especially 1
chewy, 1
like 1
the 3
tapioca 1
balls 1
that 3
form 1
the 3
'bubbles' 1
in 1
bubble 1
tea. 1

It's 1
said 1
this 1
unusual 1
drink 1
was 1
invented 1
out 1
of 1
boredom. 1

 1

序列切片

序列[start:end:stride]、序列[slice(start,end,stride)]

  • 會處理超出範圍的索引

序列[start:end:stride]

  • start 是 inclusive,預設是0開始
  • end 是 exclusive,預設是序列長度 len(序列)
    • 若 end > len(序列),則以len(序列)為準。
  • start > end ,則等同於取空串列

注意!

  • 當切割包含開頭或是尾端,建議省略降低視覺雜訊
In [26]:
# 索引 0    1    2    3    4    5    6    7
l = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
print('l[:4]    =', l[:4])  # 建議不要寫 l[0:4]
print('l[-4:]   =', l[-4:]) # 建議不要寫 l[-4:len(l)]
print('l[3:-3]  =', l[3:-3])
print('l[-3:-1] =', l[-3:-1])
print('l[5:3]   =', l[5:3]) 
print('l[1:20]  =', l[1:20])
print('lslice(1,20)  =', l[slice(1,20)])  # 等同於 l[1:20]
l[:4]    = ['a', 'b', 'c', 'd']
l[-4:]   = ['e', 'f', 'g', 'h']
l[3:-3]  = ['d', 'e']
l[-3:-1] = ['f', 'g']
l[5:3]   = []
l[1:20]  = ['b', 'c', 'd', 'e', 'f', 'g', 'h']
lslice(1,20)  = ['b', 'c', 'd', 'e', 'f', 'g', 'h']

序列[start:end:stride]

  • stride 是跳幾次切割,預設是1
  • stride 為負值則由尾端向前端取
  • stride 不得為0
In [27]:
l = [0,1,2,3,4,5]
print(l[::2])  # 從0開始,跳2格取
print(l[1::2]) # 從1開始,跳2格取
print(l[::-1]) # 從-1開始,跳1格取,即倒轉
print(l[::-2]) # 從-1開始,跳2格取
[0, 2, 4]
[1, 3, 5]
[5, 4, 3, 2, 1, 0]
[5, 3, 1]

!注意

為避免困惑,stride最好不要用負值,且建議不要與start、end並用!

In [28]:
l = [0,1,2,3,4,5]
print(l[2::2])
print(l[:-2:2] )
print(l[2:-2:2])
[2, 4]
[0, 2]
[2]
In [29]:
l = [0,1,2,3,4,5]
print(l[-2::-2])
print(l[:2:-2])
print(l[-2:2:-2])
[4, 2, 0]
[5, 3]
[4]

[練習]

回文是個正念反念都一樣的詞句,試試看要怎麼判斷字串是否為回文,
以下這些都是回文:

  • race car
  • Amor, Roma
  • 上海自來水來自海上

Hint:

  • stride如果是負值,就是從後向前取,可以有倒轉效果
  • 會用到判斷式
  • 想想看有什麼字串方法可以做到:
    • 會要把空格、標點符號都取代掉
    • 大小寫不影響判斷=>統一轉成大寫或小寫
s1 = "race car"
s1 = s1.replace(___,___) # 可能不只一個
s1 = s1.lower() 
s2 = s1[____]
__ s1==s2:
    print("是回文")
____:
    print("不是回文")

切片是產生一個新的序列

In [30]:
l0 = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
l1 = l0
l2 = l0[:] # 等同於自己(Shallow Copy)
print("id_l0 = ",id(l0))
print("id_l1 = ",id(l1))
print("id_l2 = ",id(l2))
print(l2)
id_l0 =  140186906434248
id_l1 =  140186906434248
id_l2 =  140186863440584
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']

Shallow Copy

In [31]:
# 序列中同時有可變與不可變的物件
l1 = [1,2, [3, 4]]
l2 = l1[:]
l2[0] = 4       # 修改不可變物件
print('l1:', l1, id(l1), id(l1[2]))
print('l2:', l2, id(l2), id(l2[2]))
print()
l2[2][0] = 5    # 修改可變物件
print('l1:', l1)
print('l2:', l2)
l1: [1, 2, [3, 4]] 140186863484232 140186863509064
l2: [4, 2, [3, 4]] 140186863541704 140186863509064

l1: [1, 2, [5, 4]]
l2: [4, 2, [5, 4]]

Deep Copy

In [32]:
import copy
l1 = [1,2, [3, 4]]
l2 = l1.copy()         # Shallow Copy
l3 = copy.copy(l1)     # Shallow Copy
l4 = copy.deepcopy(l1) # Deep Copy
l2[0] = 4
l2[2][0] = 5
l3[0] = 6
l3[2][0] = 7
l4[0] = 8
l4[2][0] = 9
print('l1:', l1, id(l1), id(l1[2]))
print('l2:', l2, id(l2), id(l2[2]))
print('l3:', l3, id(l3), id(l3[2]))
print('l4:', l4, id(l4), id(l4[2]))
l1: [1, 2, [7, 4]] 140186864371464 140186863400968
l2: [4, 2, [7, 4]] 140186863395848 140186863400968
l3: [6, 2, [7, 4]] 140186863506440 140186863400968
l4: [8, 2, [9, 4]] 140186863541704 140186863509064

再談串列存取

  • 右方數量可比左方多

序列取值

  • 取值方法,不管是可變或不可變的序列,只要是可迭代者都能通用

= 兩邊可放相對等的物件去取值

In [33]:
a,b,c = range(0,3)
print('a =',a,', b =',b,', c =',c)
a = 0 , b = 1 , c = 2
In [34]:
l = [1,2, [3, 4],"Hi"]
a,b,c,d  = l
print('a =',a,', b =',b,', c =',c,', d =',d)
a = 1 , b = 2 , c = [3, 4] , d = Hi
In [35]:
l = [1,2, [3, 4],"Hi"]
a,b,c,d,e  = l
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-35-63a60899540e> in <module>()
      1 l = [1,2, [3, 4],"Hi"]
----> 2 a,b,c,d,e  = l

ValueError: need more than 4 values to unpack

* 可以處理長度未知的情況

  • 會以串列的形式取值
In [36]:
# 取首尾,其他的都丟給b
l = [1,2, [3, 4],"Hi"]
a,*b,c  = l
print('a =',a,', b =',b,', c =',c)
a = 1 , b = [2, [3, 4]] , c = Hi
In [37]:
# 拿倒數的兩個元素
l = [1,2, [3, 4],"Hi"]
*a,b,c  = l
print('a =',a,', b =',b,', c =',c)
a = [1, 2] , b = [3, 4] , c = Hi
In [38]:
# 如果左邊個數較多,*標示的名稱會指向空串列
l = [1,2]
*a,b,c  = l
print('a =',a,', b =',b,', c =',c)
a = [] , b = 1 , c = 2

同一層級無法同時用一個以上的 *

In [39]:
l = [1,2, [3, 4],"Hi"]
*a,*b,c  = l
  File "<ipython-input-39-a780a528d531>", line 2
    *a,*b,c  = l
                ^
SyntaxError: two starred expressions in assignment
In [40]:
l = [1,2, [3, 4],"Hi"]
*a,(b1,*b2),c  = l
print('a =',a) 
print('b1 =',b1) 
print('b2 =',b2) 
print('c =',c)
a = [1, 2]
b1 = 3
b2 = [4]
c = Hi

串列存值

  • 存值方法只適用於可變序列(eg. 串列list)
In [41]:
l = [1,2, [3, 4],"Hi"]

l[:2] = 5,6
print(l)

l[2] = 5,6   # 是以tuple型別看待
print(l)
[5, 6, [3, 4], 'Hi']
[5, 6, (5, 6), 'Hi']

若以切片方式指派,不論右邊的數量多寡,左方會直接指向新的物件

  • 右方若數量較少,需為序列型別
In [42]:
l = [1,2, [3, 4],"Hi"]
l[:2] = 7,8,9
print(l)
[7, 8, 9, [3, 4], 'Hi']
In [43]:
l = [1,2, [3, 4],"Hi"]
l[:3] = "s"
print(l)
['s', 'Hi']
In [44]:
l = [1,2, [3, 4],"Hi"]
l[:3] = 7
print(l)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-44-aa0b60c35a28> in <module>()
      1 l = [1,2, [3, 4],"Hi"]
----> 2 l[:3] = 7
      3 print(l)

TypeError: can only assign an iterable

既然右方元素可以比左方元素少,
似乎可以來刪除串列的元素

In [45]:
l = [1,2, [3, 4],"Hi"]
l[:2]=[]
print(l)
[[3, 4], 'Hi']

串列的增刪

  • 可變序列皆可通用
  • 會直接改變串列

增加:把序列的元素加入串列的末端

  • 串列+=序列
  • 串列.extend(物件)
In [46]:
l = [1,2,3,4,5]
l+=[6,7]
print(l)
l+="Hi"
print(l)
[1, 2, 3, 4, 5, 6, 7]
[1, 2, 3, 4, 5, 6, 7, 'H', 'i']
In [47]:
l = [1,2,3,4,5]
l.extend([6,7])
print(l)
l.extend("Hi")
print(l)
[1, 2, 3, 4, 5, 6, 7]
[1, 2, 3, 4, 5, 6, 7, 'H', 'i']
In [48]:
# 因為會直接修改原本的串列,不要像字串的方法一樣,再去指派(因為會回傳`None`)
l = [1,2,3,4,5]
l = l.extend([6,7])
print(l)
None

增加:把物件加入串列的末端

  • 串列.append(物件)
In [49]:
l = [1,2,3,4,5]
l.append([6,7])
print(l)
l.append("Hi")
print(l)
[1, 2, 3, 4, 5, [6, 7]]
[1, 2, 3, 4, 5, [6, 7], 'Hi']

增加:把物件插入串列的索引位置,串列原元素往後移

  • 串列.insert(索引,物件)
    • 若索引>len(串列),則插入串列最後方
In [50]:
l = [1,2,3,4,5]
l.insert(1,[6,7])
print(l)
l.insert(1,"Hi")
print(l)
l.insert(len(l)+10,"Hello")
print(l)
[1, [6, 7], 2, 3, 4, 5]
[1, 'Hi', [6, 7], 2, 3, 4, 5]
[1, 'Hi', [6, 7], 2, 3, 4, 5, 'Hello']

刪除s[i:j:k]代表的元素

  • 串列[i:j:k]=[]
  • del 串列[i:j:k]
In [51]:
l = [1,2,3,4,5]
l[:2]=[] 
print(l)
del l[:2]
print(l)
del l     # 連名稱都刪除
print(l)
[3, 4, 5]
[5]
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-51-1e6de8ca825a> in <module>()
      5 print(l)
      6 del l     # 連名稱都刪除
----> 7 print(l)

NameError: name 'l' is not defined

拿出索引的元素,並刪除

  • 串列.pop(索引):索引預設為 len(串列)-1
In [52]:
l = [1,2,(3,4),2,3,4]
print(l.pop())
print(l.pop(2))
print(l)
4
(3, 4)
[1, 2, 2, 3]

注意!

  • 索引不得超出範圍
In [53]:
l = [1,2,3,4,5]
del l[6]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-53-8cacbaf73d3d> in <module>()
      1 l = [1,2,3,4,5]
----> 2 del l[6]

IndexError: list assignment index out of range
In [54]:
l = [1,2,3,4,5]
a = l.pop(6)
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-54-6cc12e822f3e> in <module>()
      1 l = [1,2,3,4,5]
----> 2 a = l.pop(6)

IndexError: pop index out of range

刪除第一個與物件相符的元素

  • 串列.remove(物件)

注意!

  • 若找不到相符元素會出錯
In [55]:
l = [1,2,(3,4),2,3,4]
l.remove(2)
print(l)
l.remove((3,4))
print(l)
l.remove(5)
[1, (3, 4), 2, 3, 4]
[1, 2, 3, 4]
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-55-a447b94fbed4> in <module>()
      4 l.remove((3,4))
      5 print(l)
----> 6 l.remove(5)

ValueError: list.remove(x): x not in list

刪除全部的元素

  • 串列.clear():等同於del 串列[:]
In [56]:
l = [1,2,(3,4),2,3,4]
l.clear()
print(l)
[]

[練習]

利用迴圈、與內建函式或是串列的方法,留下不重複的元素:

l=[1,2,6,1,3,2,5]
會有 1,2,3,5,6 這些元素(順序可不一)

Hint:

  • 可能的做法:把重複的刪除、把不重複的儲存到新的地方....
  • 判斷是否在串列之中,可用in或是數出現幾次串列.count()
l=[1,2,6,1,3,2,5]

for i in l:
    # 方法1:如果出現次數超過1,就刪除
    # 方法2:如果沒有出現過,就增加
    # ...
print(__)

避免迴圈內原地修改

因為迭代器仍會取用原先串列的資訊,
但中途卻更動了原先的串列,小則結果不正確,大則程式出錯中斷

In [57]:
l=[1,2,2,2,6,1,3,5]
for i in l:
    print(i,l)
    if l.count(i)>1:
        l.remove(i)
    print(" ",l)
print(l)
1 [1, 2, 2, 2, 6, 1, 3, 5]
  [2, 2, 2, 6, 1, 3, 5]
2 [2, 2, 2, 6, 1, 3, 5]
  [2, 2, 6, 1, 3, 5]
6 [2, 2, 6, 1, 3, 5]
  [2, 2, 6, 1, 3, 5]
1 [2, 2, 6, 1, 3, 5]
  [2, 2, 6, 1, 3, 5]
3 [2, 2, 6, 1, 3, 5]
  [2, 2, 6, 1, 3, 5]
5 [2, 2, 6, 1, 3, 5]
  [2, 2, 6, 1, 3, 5]
[2, 2, 6, 1, 3, 5]
In [58]:
l=[1,2,6,1,3,2,5]
for i in range(len(l)):
    print("l[{}]={}  ".format(i,l[i]),l)
    if l.count(l[i])>1:
        del l[i]
    print("\t",l)
print(l)
l[0]=1   [1, 2, 6, 1, 3, 2, 5]
	 [2, 6, 1, 3, 2, 5]
l[1]=6   [2, 6, 1, 3, 2, 5]
	 [2, 6, 1, 3, 2, 5]
l[2]=1   [2, 6, 1, 3, 2, 5]
	 [2, 6, 1, 3, 2, 5]
l[3]=3   [2, 6, 1, 3, 2, 5]
	 [2, 6, 1, 3, 2, 5]
l[4]=2   [2, 6, 1, 3, 2, 5]
	 [2, 6, 1, 3, 5]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-58-9ca89e583cf4> in <module>()
      1 l=[1,2,6,1,3,2,5]
      2 for i in range(len(l)):
----> 3     print("l[{}]={}  ".format(i,l[i]),l)
      4     if l.count(l[i])>1:
      5         del l[i]

IndexError: list index out of range
In [59]:
## 其實只會增加5次 (一開始的串列長度=5)
l = [1,2,3,4,5]
for i in range(len(l)):
    l.append(6)
print(l)
[1, 2, 3, 4, 5, 6, 6, 6, 6, 6]
# 造成無限迴圈,因為可以一直next()
l = [1,2,3,4,5]
for i in l:
    l.append(6)
print(l)

應用情境:密碼學

古典加密法-凱撒密碼

think

  • 可以直接對全文用replace嗎?
  • y -> ?

內建函式 ord()、chr()

  • ord(長度為1的字串):回傳字串的 Unicode 編碼數字
    • 半形的標點符號、字母、數字同 ASCII 編碼數字
  • chr(正整數):將 Unicode 編碼數字轉換回字串
In [60]:
print(ord("A"),ord("B"),ord("Z"))
print(ord("a"),ord("b"),ord("z"))
print(ord("A"),ord("B"),ord("Z"))
65 66 90
97 98 122
65313 65314 65338
In [61]:
print(ord("A"),chr(65),chr(ord("A")))
65 A A

應用情境:資料正規化

配合迴圈,就可以讓重複工作變得簡單、更有彈性

In [62]:
brand = "APPLE"
brand = brand.replace("A","A").replace("P","P").replace("L","L").replace("E","E")
print (brand)
APPLE
In [63]:
brand = "APPLE"
gap = ord("A")-ord("A")
print(gap)
for i in brand: #字串也是可以迭代的
    print(i,ord(i),ord(i)-gap,chr(ord(i)-gap))
    if ord(i) in range(ord("A"),ord("Z")+1):
        brand = brand.replace(i,chr(ord(i)-gap))
print(brand)
65248
A 65313 65 A
P 65328 80 P
P 65328 80 P
L 65324 76 L
E 65317 69 E
APPLE

[練習] 判斷是否為夠強度的密碼

密碼通常會要求至少有一小寫、大寫、數字,
寫一程式判斷密碼強度是否夠強。
更進階可以檢查是否包含其他標點符號。

例如:Ilove7-eleven是強度夠的密碼,包含標點符號

Hint:

  • 利用迴圈拿取每個字元
  • 判斷:字串判斷大小寫數字的方法、ord()、比較運算元
  • 分別是否紀錄有小寫、大寫、數字,可用布林型別或是數字代表
    • 只要曾經出現過,就更改;沒出現就不用處理
s = "Ilove7-eleven"
have_lower = False
have_upper = False
have_number = False
for i in s:
    if ____: 
        have_lower = True
    if ____: 
        have_upper = True
    if ____: 
        have_number = True
if have_lower and have_upper and have_number:
    print("符合密碼強度")
else:
    print("密碼強度不足")

[練習] The Python Challenge

  • 將每個字母往後移兩個
  • 標點符號不用置換
  • 字母是在編碼範圍裡置換 (y->a、z->b)
article = "g fmnc wms bgblr rpylqjyrc gr zw fylb. rfyrq ufyr amknsrcpq ypc dmp. bmgle gr gl zw fylb gq glcddgagclr ylb rfyr'q ufw rfgq rcvr gq qm jmle. sqgle qrpgle.kyicrpylq() gq pcamkkclbcb. lmu ynnjw ml rfc spj."
In [65]:
code = ord("M")-ord("K")
print (code)
print (chr(ord("K")+code))
2
M

Hint:

跟前面提到的資料正規化作法很像

  • 把字串一個個字元拿出來 => for 迴圈
  • 判斷是否是字母 => 在a~z的範圍之中
  • 是字母的話,後移 => ord()、chr()
  • 如果後移超過a~z的範圍,要再處理
  • 用另外的物件儲存字母後移(或是不後移的標點符號)
article = "g fmnc wms bgblr rpylqjyrc gr zw fylb. rfyrq ufyr amknsrcpq ypc dmp. bmgle gr gl zw fylb gq glcddgagclr ylb rfyr'q ufw rfgq rcvr gq qm jmle. sqgle qrpgle.kyicrpylq() gq pcamkkclbcb. lmu ynnjw ml rfc spj."
new_article = ""
code = ___
for i in article:
    if _____:
        if ____ >= ord("z"):
            new_article+= ____
        else:
            new_article+= ____
    else:
        new_article+= i
print (new_article)

think

  • 如果沒有解密表...

[練習] 印出每個字母出現頻率

  • 同一個字母只會印一次
  • 只印出英文字母

Hint:

  • 前面有練習取不重複元素
  • 只印出在a~z範圍的字母
  • 序列.count(物件):可算出序列中物件出現幾次

補充知識-串列生成式

在短短一行程式碼就能產生想要資料內容

  • 運算式 for 名稱 in 可迭代者
  • 運算式 for 名稱 in 可迭代者 if 運算式

調分公式:開根號,再乘10

In [66]:
ori = [32,56,58,62,79,82,98]
new = []
import math
for i in ori:
    new.append(int(math.sqrt(i)*10))
print(new)
[56, 74, 76, 78, 88, 90, 98]
In [67]:
import math
ori = [32,56,58,62,79,82,98]
new = [int(math.sqrt(i)*10) for i in ori]
print(new)
[56, 74, 76, 78, 88, 90, 98]

過濾比平均值低的分數

In [68]:
l = [32,56,58,62,79,82,98]
l1=[]
for i in l:
    if i<(sum(l)/len(l)):
        l1.append(i)
print(sum(l)/len(l),l1)
66.71428571428571 [32, 56, 58, 62]
In [69]:
l = [32,56,58,62,79,82,98]
l1=[i for i in l if i<(sum(l)/len(l))]
print(sum(l)/len(l),l1)
66.71428571428571 [32, 56, 58, 62]

誰花的錢超過100元

In [70]:
# 使用序列切片
l = [("A",5,60,23),("B",2,45,38),("C",70,85,66),("D",2,3,9)]
l1=[ (i[0],sum(i[1:])) for i in l if sum(i[1:])>100]
print(l1)
[('C', 221)]
In [71]:
# 使用指派
l = [("A",5,60,23),("B",2,45,38),("C",70,85,66),("D",2,3,9)]
l1=[ (name,sum(rest)) for name,*rest in l if sum(rest)>100]
print(l1)
[('C', 221)]

補充知識-翻轉

序列切片[::-1]

  • 會回傳切片後的結果
In [72]:
l = [1,2,3,4,5]
s = "Hello"
print(l[::-1])
print(s[::-1])
[5, 4, 3, 2, 1]
olleH

reversed(序列)

  • 會回傳一個迭代器
In [73]:
l = [1,2,3,4,5]
lr = reversed(l)
print(next(lr))
print(list(reversed(l)))
5
[5, 4, 3, 2, 1]
In [74]:
s = "Hello"
print(list(reversed(s)))
['o', 'l', 'l', 'e', 'H']

可變序列.reverse()

  • 可變序列原地翻轉
In [75]:
l = [1,2,3,4,5]
l.reverse()
print(l)
[5, 4, 3, 2, 1]

補充知識-搜尋

計算元素出現的次數

  • 序列.count(元素,start,end):在切片[start:end]中,元素出現幾次
  • start、end預設為空,意即全搜尋
In [76]:
s = "Hi PyLadies"
print(s.count("i"))
print(s.count("i",3,5))
2
0
In [77]:
l=[1,2.5,"Hello",2.5,2017,2017.0]
print(l.count(2017)) # 2017.0 也會被算進去
2

其實只要有了迴圈、物件的概念,也可以自己實作

In [78]:
l=[1,2.5,"Hello",2.5,2017,2017.0]
count = 0
for i in l:
    if i==2017:
        count+=1
print(count)
2

找出元素的索引值=>找不到會報錯

  • 序列.index(元素,start,end):在切片[start:end]中,從左邊開始找
  • 字串.rindex(元素,start,end):在切片[start:end]中,從右邊開始找
  • start、end預設為空,意即全搜尋
In [79]:
l=[1,2.5,"Hello",2.5,2017,2017.0]
print(l.index("Hello"))
print(l.index(2.5))
2
1
In [80]:
s = "Hi PyLadies"
print(s.index("i"))
print(s.rindex("i"))
print(s.index("i",3,5))
1
8
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-80-6c7a3676800e> in <module>()
      2 print(s.index("i"))
      3 print(s.rindex("i"))
----> 4 print(s.index("i",3,5))

ValueError: substring not found

找出元素的索引值(字串限定)=>找不到回傳-1

  • 字串.find(元素,start,end):在切片[start:end]中,從左邊開始找
  • 字串.rfind(元素,start,end):在切片[start:end]中,從右邊開始找
  • start、end預設為空,意即全搜尋
In [81]:
s = "Hi PyLadies"
print(s.find("i"))
print(s.rfind("i"))
print(s.find("i",3,5))
1
8
-1

判斷開頭或結尾出現元素(字串限定)

  • 字串.startswith(元素,start,end):在切片[start:end]中,從左邊開始找
  • 字串.endswith(元素,start,end):在切片[start:end]中,從右邊開始找
  • start、end預設為空,意即全搜尋

應用情境-找出不安全的網站

In [82]:
l = ["https://www.google.com","http://www.slime.com.tw/","http://shopping.pchome.com.tw/","https://www.facebook.com/"]
danger_list = [i for i in l if not i.startswith("https")]
print(danger_list)
['http://www.slime.com.tw/', 'http://shopping.pchome.com.tw/']

應用情境-找出副檔名為jpg的圖片

In [83]:
l = ["photo.png","tmp.jpg","snapshot.png","test.jpg","temp"]
jpg_list = [i for i in l if i.endswith(".jpg")]
print(jpg_list)
['tmp.jpg', 'test.jpg']

補充知識-zip()、enumerate()

  • 回傳的型別皆為迭代器

zip(序列1,序列2)

  • 回傳將兩序列配對成tuple的串列
  • 若兩序列長度不一,則以最短的串列為主
In [84]:
l1 = (1,2,3,4,5)
l2 = ['a','b','c','d','e']
s = zip(l1,l2)
print(list(s))
[(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd'), (5, 'e')]
In [85]:
l1 = [1,2,3,4,5]
l2 = ['a','b','c','d']
s = zip(l1,l2)
print(list(s))
[(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')]

應用情境-學生姓名與成績

In [86]:
l1  = ['a','b','c','d','e']
l2 = [1,2,3,4,5]
for i in range(len(l1)):
    print(l1[i],"的成績為",l2[i])
a 的成績為 1
b 的成績為 2
c 的成績為 3
d 的成績為 4
e 的成績為 5
In [87]:
l1  = ['a','b','c','d','e']
l2 = [1,2,3,4,5]
for name,score in zip(l1,l2):
    print(name,"的成績為",score)
a 的成績為 1
b 的成績為 2
c 的成績為 3
d 的成績為 4
e 的成績為 5

enumerate(序列,起始值)

  • 常用於同時取得索引與元素
  • 起始值預設為0
In [89]:
l  = ['a','b','c']
for index,value in enumerate(l):
    print(index,value)
0 a
1 b
2 c
In [90]:
l  = ['a','b','c']
for index,value in enumerate(l,1):
    print(index,value)
1 a
2 b
3 c
In [92]:
l = [5.5, 9, 10]
for i in range(3):
    print("員工{}的薪水".format(i+1), l[i]*100+22000)
員工1的薪水 22550.0
員工2的薪水 22900
員工3的薪水 23000
In [93]:
l = [5.5, 9, 10]
for i,value in enumerate(l,1):
    print("員工{}的薪水".format(i), value*100+22000)
員工1的薪水 22550.0
員工2的薪水 22900
員工3的薪水 23000

學習資源對應