本章節介紹 Python 程式語言常見的語法規則。
# 兩個指派陳述句
a = 'good'
b = 'bad'
# 這是省略了 tuple () 括號的單一陳述句
a, b = 'good', 'bad'
# 這是跨越兩行的單一陳述句
a, b = 'good',\
'bad'
# 這也是跨越兩行的單一陳述句
a, b = ('good2',
'bad2')
# 這是寫在同一行的兩個陳述句
a = 'good'; b = 'bad'
以下原則適用於變數、函式、類別、類別方法、類別屬性等命名。
以下慣例不是強制要求的原則,但實際一般設計都是會遵循這樣的原則。
__name__
,這通常是 Python 系統本身的定義在使用的。__x
,定義在類別中代表是該類別私有(private)的。_x
,不會被 from module import *
這樣的述句 import。_
這個名字,因為這是在互動式運算時,Python 用來儲存前一次運算結果的。2.2 節中列出的算數運算子,除了 NOT ‘~’ 是一元運算子(uniary operator)以外,其他的運算子都是二元運算子(binary operator)。 針對兩個運算元(operand)的二元運算,常見將運算結果直接指派給其中一個運算元的同一個變數,例如:
x = x + y
這樣的指派陳述可以使用擴增的述句來取代:
x += y
視不同的資料類型而定,這樣的擴增述句有時也隱含著就地變更的最佳化處理。
x, y = 3, 5
x -= 1; y **= x
print('x =', x, ', y =', y)
x = 2 , y = 25
2.4 節中介紹過一般的卸載,任何序列或可迭代(iterable)物件的值可以直接指派給一個變數名字的序列,只要對應的元素數量是一樣的就可以。 基本的語法如:
a, b, c, d = [1, 2, 3, 4]
Python 提供了延伸的語法,在變數名字前加上前置星號 **‘*’** ,就允許等號左邊的變數數量可任意調整:
延伸可迭代卸載 | 結果 |
---|---|
a, *b = [1, 2, 3, 4] |
a = 1, b = [2, 3, 4] |
a, *b, c = [1, 2, 3, 4] |
a = 1, b = [2, 3], c = 4 |
*a, b = [1, 2, 3, 4] |
a = [1, 2, 3], b = 4 |
# range 是可迭代物件,一樣適用延伸語法的卸載
x, *y = range(10)
x, y
(0, [1, 2, 3, 4, 5, 6, 7, 8, 9])
del
述句可以用來刪除變數、物件、物件屬性、序列的元素、序列的片段等。
# 刪除序列片段元素
L = list(range(10))
print('原來 L =', L)
del L[:10:2]
print('刪除某元素後 L =', L)
原來 L = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 刪除某元素後 L = [1, 3, 5, 7, 9]
# 刪除變數(及物件)
del L
print(L)
--------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-9-824d9fb6d651> in <module> 1 # 刪除變數(及物件) 2 del L ----> 3 print(L) NameError: name 'L' is not defined
assert
主要用於除錯目的。 程式碼的組織,通常會依據功能切割成不同的小區塊。 每個區塊的運算或多或少會假設某些前提,這些前提可能是在其他區塊已經處理過的結果,可能是系統必需要提供的資源,而在滿足前提的狀況下才得以繼續完成運算。
assert(條件)
就是用來檢查這樣必要滿足的前提條件;若滿足斷言,程式繼續執行;若不滿足斷言條件,Python 會發出 AssertionError 的錯誤,然後中斷執行。
# 斷言序列長度一定滿足某個數量
L = list(range(10))
assert(len(L) == 10)
Python 程式的語法中,區塊的程式碼使用 *space* 或 *tab* 縮排的層次來區隔不同執行條件的陳述句。 以下列的 if
條件述句為例,所有在 x > y
條件成立才會執行的程式區塊,都同樣放在4個 spaces(預設等於1個tab)的第一層縮排裡。
if x > y:
z = x - y
x = x + y
這樣的區塊陳述式,都會有一個以冒號 ‘:’ 結尾的區塊標頭陳述式。 如上例中的 if x > y:
。 若需要巢狀的區塊陳述式,可以透過增加縮排的層次來達成。
除了前置 ‘#’ 符號用來註解外,Python 另外支援連續三個單引號或雙引號的區塊性文字註解。這樣的區塊性文字註解,若置於模組檔案、類別、函式、的最前面,稱為 *docstrings*(其實這才是這語法設計的本意),會被儲存於該物件的 __doc__
屬性中。
對於一般陳述句,Python 會一行一行往下執行,直到碰到改變流程的陳述句:
if-elif-else
: 條件陳述式,選擇性執行。while-else
: 條件式迴圈,迭代執行。for-else
: 序列式迴圈,迭代執行。pass
: 佔位述句。break
: 中斷。continue
: 繼續。try-except-finally
: 例外處理。raise
: 觸發例外狀況。if-elif-else
條件陳述式由一個 if
邏輯測試條件,中間可有可無的 elif
測試條件,以及最後也是可有可無的 else
條件所組成。 每個條件都有對應的陳述式區塊,在條件測試為 True 時會被執行。 一般的形式像:
if test1:
statements1
elif test2:
statements2
else:
statements3
簡單的 if-else 指派陳述如:
if test:
x = a
else:
x = b
可以簡化成一行三元運算的陳述句:
x = a if test else b
a, b = 'good', 'bad'
# 試著改變 x = 'good', 或 x = 'bad' 看看條件測試的結果
x = 'excellent'
#x = 'good'
#x = 'bad'
if x == a:
print('x is also good')
elif x == b:
print('x is bad')
else:
print('neither good nor bad, x is {}'.format(x))
neither good nor bad, x is excellent
# 三元運算陳述句
print(a if x != 'bad' else b)
good
while
迴圈包含了一個標頭的邏輯條件測試式,一個或多個縮排的區塊陳述式;以及一個可有可無的 else
條件,當迴圈不是被 break
離開的時候會被執行。
while test:
statements
else:
loop_not_break_statements
# 一個一個字元複製直到字串完全相等
x = 'excellent'
c = ''
while c != x:
c += x[len(c)]
print(c)
excellent
# 註: Python 的空字串會被視為是其他字串的子字串
while c in x:
print(c)
if len(c) > 1:
c = c[:-1]
elif len(c) == 1:
# 試看看 c = '0' 和 c = '' 的結果有甚麼不同?
c = '0'
else:
break
else:
print('exit while loop without break')
print('c =', c)
excellent excellen excelle excell excel exce exc ex e exit while loop without break c = 0
for
迴圈是一個通用的迭代器(iterator),可以用來走訪任何*序列容器的元素或可迭代(iterable)物件*。 for
迴圈區塊標頭從要走訪的 iterable_object 中循序指派內容物參考到 item,然後針對每個 item 重複執行區塊中的陳述句。
for item in iterable_object:
statements
else:
statements
於 while-else 相同,for
後面接著的 else
條件也是可有可無,當迴圈不是被 break
離開的時候會被執行。
for c in x:
print(c)
e x c e l l e n t
for c in x:
# 試看看在 '0123456789' 字串中加入任意一個 'e', 'x', 'c', 'l', 'n', 't' 的字元,結果有甚麼不同?
if c not in '0123456789l':
print(c)
else:
break
else:
print('all elements in x are iterated without break')
e x c e
# 沒作甚麼事,單純占用 CPU 時間的迴圈
for i in range(2**25):
pass
跳離最近的 for
或 while
迴圈,以及與其相伴的 else
區塊(如果有的話)。
for i in range(10):
if i > 5: break
print(i)
0 1 2 3 4 5
在迴圈中出現時,忽略以下所有述句,回到迴圈標頭繼續下一輪迴圈。
n = list(range(10))
for i in n:
if i < 5: continue
print(i)
5 6 7 8 9
程式時若發生非預期的例外狀況,Python會中斷執行並發出錯誤訊息。 若要避免非預期的程式中斷執行,可以將可能會出現錯誤的程式碼包在 try
區塊中,然後指定 except
例外處理子句。 如果沒有錯誤發生,則 else
(optional)區塊的陳述會被執行。 不管錯誤有沒有發生,finally
子句永遠會被執行,用來處理善後事宜。基本的句型在 try
之後至少要有一個 except
或 finally
。
try:
statements
except type:
statements
,或
try:
statements
finally:
statements
,或完整述句
try:
statements
except:
statements
else:
statements
finally:
statements
except
子句後若沒有指定例外的類型,則任何未被處理的例外都會在此處理。
# 注意: 為了展示 try-except-finally 語法,Divide() 函式回傳型態不一致,但這不是好的函式寫法
def Divide(x, y):
try:
result = x / y
except ZeroDivisionError:
return 'divided by zero!'
else:
return result
finally:
print('executing finally clause')
print(Divide(6, 3))
print(Divide(6, 0))
executing finally clause 2.0 executing finally clause divided by zero!
使用 raise
述句主動觸發例外狀況。
# 改寫上面的範例,加入任意主動觸發例外,並新增處理任何尚未處理的例外的子句。
def Divide(x, y):
try:
#result = x / y
raise IndexError
except ZeroDivisionError:
return 'divided by zero!'
except:
print('some exception raised')
else:
return result
finally:
print('executing finally clause')
print(Divide(6, 3))
some exception raised executing finally clause None
初學者最直覺也最常用的程式結構,就是一行一行程式碼循序往下寫直到結束。 這種寫法用來解決一般稍微複雜的問題時,就很容易遇到難以維護或可讀性不佳的問題。 工程上我們常會採用一種簡化問題的技巧: Divide and Conquer,這樣的手法在軟體程式設計的領域到處可見。 在程式中使用函式,是將大問題拆解成小問題的一個基本步驟。 所以經常可以看到一個大型一點的程式,內容的結構其實就是一堆小工具函式的集合。 Python 中用來定義函式的關鍵保留字如下:
def
: 函式定義。return
: 函式運算結果返回。lambda
: 匿名函式。yield
: 定義生成函式的返回結果。函式以 def
關鍵字及指定的函式名字起始,後面使用小括號接著函式運算需要的所有參數。 區塊陳述包含函式所需的運算,運算結果的值使用 return
述句返回,若未明確指定,預設返回值為 None
。
def name(arg_name1,..., arg_nameN=value):
statements
return value
可用參數定義格式 | 格式說明 |
---|---|
arg_name1 |
一般參數,以位置或名字匹配 |
arg_nameN=value |
同一般參數,明確指定預設值 |
函式呼叫
name(arg_value1,... arg_nameN=value,... *iterable_arg, **dict_arg)
可用參數定義格式 | 格式說明 |
---|---|
arg_value1 |
以位置匹配傳遞參數 |
arg_nameN=value |
以名字匹配傳遞參數 |
*iterable_arg |
以序列或可迭代物件傳遞,並根據位置順序卸載匹配 |
**dict_arg |
以 dict 傳遞,並根據 key 卸載匹配 |
# 定義一個函式 Add(): 印出參數相加的結果
def Add(a, b, c):
print(a + b + c)
# 一般參數傳遞
Add(2, 3, -4)
# 位置及名字匹配
Add(2, c=-4, b=3)
# 序列卸載匹配
larg = [2, 3, -4]
Add(*larg)
# 可迭代物件卸載匹配
Add(*range(2,5))
# dict 卸載匹配
darg = {'a':2, 'b':3, 'c':-4}
Add(**darg)
1 1 1 9 1
# 定義一個函式 Multiply(): 返回兩個輸入參數相乘的結果
def Multiply(a, b):
return a * b
print(Multiply(2, 3))
6
在設計程式時,建議遵循以下原則:
return
來回傳,讓呼叫端來決定是否覆蓋原物件內容。# 指派新物件不影響呼叫端
def f(a):
# 一開始 a 與呼叫端的 b 參考同一物件
print('a =', a, 'id =', id(a))
# 指派新的 int 物件
a = 9
# 現在 a 參考到新物件
print('a =', a, 'id =', id(a))
b = 5
# before function call
print('b =', b, 'id =', id(b))
f(b)
# after function called
print('b =', b, 'id =', id(b))
b = 5 id = 140708452513728 a = 5 id = 140708452513728 a = 9 id = 140708452513856 b = 5 id = 140708452513728
# 可就地變更的類別,兩邊內容是同一份
def f_list_args(L1, L2):
print('L1 =', L1, 'id =', id(L1))
# 指派新的 list 物件
L1 = list(range(7, 10))
print('L1 =', L1, 'id =', id(L1))
print('L2 =', L2, 'id =', id(L2))
# 不建議在函式中直接更改參數物件的元素內容。
L2[0] = 'changed'
print('L2 =', L2, 'id =', id(L2))
x = [2, 3]
y = [4, 5, 6]
# before function call
print('x =', x, 'id =', id(x))
print('y =', y, 'id =', id(y))
f_list_args(x, y)
# after function called
print('x =', x, 'id =', id(x))
print('y =', y, 'id =', id(y))
x = [2, 3] id = 2438221060032 y = [4, 5, 6] id = 2438221060288 L1 = [2, 3] id = 2438221060032 L1 = [7, 8, 9] id = 2438220972352 L2 = [4, 5, 6] id = 2438221060288 L2 = ['changed', 5, 6] id = 2438221060288 x = [2, 3] id = 2438221060032 y = ['changed', 5, 6] id = 2438221060288
# 有需要回傳運算結果就用 return,讓呼叫端自己決定覆蓋原物件內容。
def min_max_scaler(vec_in):
min_v = min(vec_in)
max_v = max(vec_in)
interval = max_v - min_v
vec_out = [(float(v) - min_v) / interval for v in vec_in]
return vec_out, interval
vec = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
# before function call
print('vec =', vec, 'id =', id(vec))
vec, _ = min_max_scaler(vec)
# after function called
print('vec =', vec, 'id =', id(vec))
vec = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] id = 2438221058112 vec = [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0] id = 2438221059520
lambda
通常用來定義非常簡短的函式,以及需要 callback 函式的參數傳遞。 基本形式為:
lambda arg1, ..., argN: expression
lambda
匿名函式的內容是單一運算,而不是區塊陳述句,設計的原意本來就不是用來執行複雜的運算。
# 匿名函式沒有名字,但匿名函式的物件還是可以把參考指定給變數
func_add = lambda x, y: x + y
func_multiply = lambda x, y: x * y
print(func_add(2 ,3))
print(func_multiply(2, 3))
5 6
# lambda 常見用來傳遞函式物件,當成 callback function
def calc(op_func, a, b):
return op_func(a, b)
print(calc(func_add, 2, 3))
print(calc(func_multiply, 2, 3))
5 6
一般函式用 return
返回一次性的運算結果,相同的輸入參數可以預期得到相同的運算結果。 生成函式返回的是一個迭代子(iterator),使用 yield
返回每次迭代運算的結果。 生成函式的呼叫要使用 for
迴圈或內建函式 next()
。
def name(arg,..., arg=value):
statements
yield value
# 小於某數的偶數數列生成函式
def evenrange(n):
for i in range((n + 1) // 2):
yield i * 2
# 在 for 迴圈中使用
for ei in evenrange(7):
print(ei, end=', ')
0, 2, 4, 6,
# 使用內建函式 next()
evengen = evenrange(7)
print(next(evengen), ', ', next(evengen), ', ', next(evengen), ', ', next(evengen))
# 超過數列盡頭會發出 StopIteration 的例外狀況
print(next(evengen))
0 , 2 , 4 , 6
--------------------------------------------------------------------------- StopIteration Traceback (most recent call last) <ipython-input-31-88a00559f698> in <module> 4 5 # 超過數列盡頭會發出 StopIteration 的例外狀況 ----> 6 print(next(evengen)) StopIteration:
生成運算表示使用小括號將一個緊接著 for
的運算表示句包起來,對於序列容器或可迭代物件 S 進行操作,返回一個迭代子(iterator)。
生成運算表示 | 說明 |
---|---|
(運算表示句 for x in S) |
針對每個 S 的成員 x 做運算 |
(運算表示句 for x in S if 條件) |
針對每個*符合條件*的成員 x 做運算 |
當生成運算表示用在函式呼叫的參數,而且只有一個參數時,括號可以省略。
# 小於某數的奇數數數列生成運算表示
n = 7
oddgen = (i * 2 + 1 for i in range((n + 1) // 2))
print(oddgen)
list(oddgen)
<generator object <genexpr> at 0x00000237B14BFC80>
[1, 3, 5, 7]
# 生成運算表示直接使用在函式參數中
list(i * 2 + 1 for i in range((n + 1) // 2))
[1, 3, 5, 7]