註:本文同步刊載在Medium,若習慣Medium的話亦可去那邊看呦!
在上一篇文章中我們講了if...elif...else, while, for, 以及range,
我們也提到了像range()這樣子的方法,是用來生成一個可迭代的Python物件。
而只要可迭代的話,我們通常就可以將其轉成一個list,如上一篇文章那樣。
不管是if, while, for等,都可以使用多層的架構,
對於while跟for來說,其處理方式也是類似多層if的概念,
同時外層內層的東西不同也是可以的。
例如:
while xxx:
ooo
rrr
while yyy:
uuu
vvv
www
這個雙重迴圏,執行的順序應該是:
xxx成立時,進入迴圏->ooo->rrr->
yyy成立時,進入迴圏->uuu->vvv->
內層迴圏完成一次,重新檢查yyy,若成立則繼續運行,
否則就會執行完www後,回到xxx檢查。
(所以說內層的執行完,才會輪到外層)
今天我們要介紹一個在Python中很好用且常用的東西,
它也用到了for...in...的方法,名字叫做list comprehension,
可以用來快速簡單地生成list,
中文目前沒有統一的叫法,可以叫成串列生成式/串列表達式/串列解析式都行。
其基本形式像這樣:[算式 for 單項 in 迭代項目]
舉例來說,假設我們要一個從0~9的串列,我們可以這樣寫:
>>> list(range(9))
[0, 1, 2, 3, 4, 5, 6, 7, 8]
>>> [i for i in range(9)]
[0, 1, 2, 3, 4, 5, 6, 7, 8]
串列生成式的意義在於每次從迭代的項目當中取出一個單項,
接下來將這個單項透過算式進行加工處理,才放到list當中。
所以除了基本的方法以外,我們也可以用if條件式來進行過濾,只要在後面加上if條件式即可。
>>> [2 * i for i in range(9) if i % 2 == 0] # 取0~8當中能整除2的數,每個都先乘以2再加到list
[0, 4, 8, 12, 16]
另一方面,我們也可以利用多重的for in來製造出雙層的list或者組合性質的東西。
我們前面提到過list裡面的element也可以是list,
如果我們今天要表達一組4 * 3的格子,格子裡面一開始只放零的話,
可以寫成:
>>> [[0 for i in range(3)] for j in range(4)] # 一層一層看就會理解等於[0, 0, 0]重複4次
[[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]
如果我想要把所有的座標(從(0, 0)起算)的組合寫到一個list呢?
>>> combo = [(row, col) for row in range(4) for col in range(3)] # 留意4跟3的順序
>>> combo
[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2), (3, 0), (3, 1), (3, 2)]
(註:像這樣子在存取每一個格子都需要指定兩層index的串列,
我們稱之為二維串列,代表它有兩個維度。)
字典和集合也有生成式,其概念和列表生成式一致,
只是字典的話就會是{key:value for 單項 in 迭代項} (必須給出key跟value的部分)
集合的話除了外面是大括號外,和列表生成式一致。
接下來我們要來談一個程式語言很重要的東西:函式(function)
還記得前面我們有做過計算圓面積的練習對吧?
計算圓面積明明是一個不會改變的公式,
但我們還是要反覆改動代入值,這樣顯得有點麻煩。
為了不要浪費時間在做相同的事情上,
我們可以將寫好的一段程式碼(比如剛剛講的算面積),定義成函式,
就像數學的函式那樣,公式已經寫好了,只差將要算的東西代入進去就好。
一個函式的程式架構一般如下:
def 函式名(變數1, 變數2, 變數3, ...):
xxx
yyy
...
return ooo
def代表define(定義),後面接你的命名,
括號內是放入你需要從呼叫函式的對象那邊得到的變數(或者稱為參數),
也可以都不放,表示你不需要從外界傳入;
接著就在這個函式內按照一般程式碼的流程來撰寫,
最終如果這個函式需要回報一些訊息的時候,則需要進行return(回傳)。
我們拿圓面積計算為例:
def area(r):
pi = 3.14
return 3.14 * r ** 2 # 會將計算完的結果輸出到呼叫它的地方
由於Python是腳本式語言,
一個函式必需要先被定義或引入後,
才能夠被使用,所以我們必須在使用(或稱呼叫)函式前,
先將它定義完,在下一行開始才能夠使用它。
# area(1) 不能放在這邊,因為我們還沒有定義area函式!
def area(r):
pi = 3.14
return 3.14 * r ** 2
print('半徑長為1的圓,其面積為:' + str(area(1)))
print('半徑長為3的圓,其面積為:' + str(area(3)))
執行結果如下:
C:\Users\Desolve>python fromzero.py
半徑長為1的圓,其面積為:3.14
半徑長為3的圓,其面積為:28.26
假設我們今天不滿足圓周率pi只用3.14的話,
除了用math模組的pi外(引入模組這部分之後再講),
那麼就必須將pi也一併設定為可以拿進來用的變數。
但是一般來說,像pi這類型的常數,
我們會希望**「不特別講的話就按照預設的來」,
這時候我們可以利用在給入的變數的後面加上等號及一個值**,
一旦這個變數沒有被指定,就會使用預設值。
此外,很多時候乍看之下,我們會容易不曉得一個函式中,
每一個位置先後要代入什麼才是對的順序,
為此,Python提供我們可以指定變數名稱的方式,
只要輸入時有帶上變數名稱,即便順序和原先函式定義給的順序不一樣,
Python依舊可以正確地幫我們對應上。
def area(r, pi=3.14):
return pi * r ** 2
print('半徑長為1的圓,其面積為:' + str(area(1, 3.14159))) # 指定圓周率
print('半徑長為3的圓,其面積為:' + str(area(3))) # 不指定圓周率,使用預設值3.14
print('半徑長為3的圓,其面積為:' + str(area(pi=3.141, r=3))) # 順序會被自動對上
結果如下:
C:\Users\Desolve>python fromzero.py
半徑長為1的圓,其面積為:3.14159
半徑長為3的圓,其面積為:28.26
半徑長為3的圓,其面積為:28.269
同樣的,在函式內部也可以定義函式,
一樣的概念,要有定義過的才能被呼叫使用,
同時,可以接受參數,但也可以直接用其外層得到的變數。
def printAll(r, pi=3.14):
def area():
return pi * r ** 2
def perimeter():
return 2 * pi * r
# 下面{}的用法是所謂的format,可以將多個變數按照順序放置到{}中
print('半徑 = {}的圓,其面積 = {},周長 = {}'.format(r, area(), perimeter()))
printAll(3, 3.14159)
執行結果如下:
C:\Users\Desolve>python fromzero.py
半徑 = 3的圓,其面積 = 28.27431,周長 = 18.849539999999998
我們前面已經提過,
變數就像是將一個標籤(變數名稱)貼在一個裝有東西(資料)的箱子。
那麼,你家的急救箱和我家的急救箱,
雖然名字都是急救箱,但應該是指不一樣的東西。
在Python或其他程式語言中也一樣,
一個名稱,有屬於它存在的適用範圍,我們稱之為命名空間。
如果在最外面(也就是沒有在函式中)命名一個變數的時候,
任何人應該都看得到這個變數的存在,並且可以自由使用它,
我們稱之為全域變數。
那麼在函式內呢?
如果今天在公共的地方有一個急救箱,你又自己去買了一個急救箱回來,
那麼在命名時,你在你家稱呼的急救箱,自然是你自己買的那個,而非公共的。
(稱為區域(local)變數)
除非你都用公共的,那麼自然你指的還是那個公共的急救箱(函式內可以取得全域變數)
如果打定主意要用全域變數,且有要進行修改的話,請在函式內使用global這個關鍵字。
fak = 'global' # First-Aid Kit
def home1():
print(fak) # 直接取得全域的變數,不做修改
def home2():
fak = 'h2' # 定義一個local的變數,所以修改到的變數跟全域的fak無關
print(fak)
def home3():
global fak # 告訴Python現在要用的就是全域的那個fak
fak = 'h3'
print(fak)
print('Before:')
print(fak)
print('\nhome1:')
home1()
print('After home1:')
print(fak)
print('\nhome2:')
home2()
print('After home2:')
print(fak)
print('\nhome3:')
home3()
print('After home3:')
print(fak)
所以我們可以看到這三者之間的差異,
一個只印出全域變數,一個只使用區域變數,第三個則是不但使用了全域變數且修改了它。
C:\Users\Desolve>python fromzero.py
Before:
global
home1:
global
After home1:
global
home2:
h2
After home2:
global
home3:
h3
After home3:
h3
註:其實還有更細節的部分,但講太多恐怕對初學者容易造成混淆,
我們還是先講這兩種就好,其他的在碰到狀況,或有興趣的話,
再請讀者搜尋變數範圍的部分。
那麼我們來做個練習吧!
那就明天見啦!
print('半徑 = {}的圓,其周長 = {},面積 = {}'.format(r, area(), perimeter()))
參數應為.format(r, perimeter(), area()))
你是對的XD
感謝幫忙勘誤,已經進行更正囉!