周楷翔 @ 資芽 2022 北區 Py 班
普通的工程師寫出電腦看得懂的程式,好的工程師寫出人看得懂的程式。
為了確保人類容易看懂你的程式,大家約定了一系列的規則。
今天,我們會介紹 PEP8 - Style Guide for Python Code
完整規範:https://peps.python.org/pep-0008/
Be Pythonic
# Add 4 spaces (an extra level of indentation) to distinguish arguments from the rest. def long_function_name( var_one, var_two, var_three, var_four): print(var_one) # Aligned with opening delimiter. foo = long_function_name(var_one, var_two, var_three, var_four) # Hanging indents should add a level. foo = long_function_name( var_one, var_two, var_three, var_four)
不要寫出這種東西
if a: print(b) foo = long_function_name(var_one, var_two, var_three, var_four) def long_function_name( var_one, var_two, var_three, var_four): print(var_one)
運算如果要換行,把運算符放在前面
(想想直式運算)
income = (gross_wages + taxable_interest + (dividends - qualified_dividends) - ira_deduction - student_loan_interest)
如果一行太長了(原則上控制在 79 個字),可以用 \
換行
with open('/path/to/some/file/you/want/to/read') as file_1, \ open('/path/to/some/file/being/written', 'w') as file_2: file_2.write(file_1.read())
適度地加上空格,可以讓程式碼更好讀
PEP-8 建議「優先度最低的 operator」的前後需要空格
但也有人認為所有 operator 前後都要空格
# PEP-8: i = i + 1 i += 1 x = x*2 - 1 c = (a+b) * (a-b) # Another convention: i = i + 1 submitted += 1 x = x * 2 - 1 c = (a + b) * (a - b)
拜託不要…
# Wrong i=i+1 i+=1 x= x *2-1 c =(a +b ) *( a - b )
不要在括號裡面放空格
# Correct: spam(ham[1], {eggs: 2}) foo = (0,) # Wrong: spam( ham[ 1 ], { eggs: 2 } ) bar = (0, )
把空格放在逗點、冒號後面,而非前面
# Correct: if x == 4: print(x, y) # Wrong: if x == 4 : print(x , y)
寫程式最難的部份:想出一個有意義的變數名稱
seen_values
好於 data
square(x)
好於 f(x)
lowercase_with_underscore
get_average_score_from_list(score_list)
_
class_
, str_
CapWords
如何取個好名字:
username_list
is_
, has_
開頭
is_reserved
, has_stored
print_greeting_message()
vs. get_greeting_message()
download_from_url(url)
迴圈裡面的命名:不要一直 i
啦 ==
for i in range(0, 10): for j in range(0, 10): print(table[i][j]) for row_number in range(0, 10): for col_number in range(0, 10): print(table[row_number][col_number]) for row in table: for cell in row: print(cell)
pip install autopep8
autopep8 --in-place my_code.py
遵守規範是一回事,寫出「好看的」程式是另一回事。
記得,普通的工程師寫出電腦看得懂的程式,好的工程師寫出人看得懂的程式。
最後,規定是死,人是活的
如果按照 PEP 8 讓你的 code 變得更難讀,請大膽地直接捨棄規則
程式執行總是會遇到一些奇怪的狀況,而你要想辦法處理這些程式會壞掉的情境
>>> while True print('Hello world')
File "<stdin>", line 1
while True print('Hello world')
^^^^^
SyntaxError: invalid syntax
>>> 10 * (1/0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
什麼叫做奇怪的狀況?
舉個例子:
# 算平均 numbers = list(map(int, input().split(" "))) print(sum(numbers) / len(numbers))
考慮以下幾個情境
input = "" # 空字串 input = "hello world" input = "1 2<tab>3"
有些錯誤是可以事先預防的(例如上面的空字串)
但有些錯誤遇到就是遇到了,沒辦法預防
嘗試執行一段程式,然後抓住例外
try: 你的 code except: 你的 code 爛掉就會跑來這裡
舉個例子
try: [].index(1) except: print('1 not in list')
try: print(1 / 0) except: print('cannot divide by 0')
沒辦法接住 indentation error 跟 syntax error
try: 1 / 0 1 / 0 sdfsd while qwe except: print("你的 code 爛到這行印出來了")
各種不同的 exception
ZeroDivisionError
:國小老師說過不能除以 0,因為蛋糕不能分給 0 個人TypeError
:傳入的變數型態錯誤 e.g. int([1, 2, 3])
ValueError
:傳入的型態對了但值是錯的 e.g. int("hi~")
KeyError
:dict 或 list 的 key 不存在還有很多不同的 exception 是以後才會碰到的
(例如讀檔時可能遇到 OSError
)
如何知道是什麼錯誤?
try: print(1 / 0) except Exception as e: print(e, type(e)) # division by zero <class 'ZeroDivisionError'>
如何接住特定的錯誤
也許有些錯誤是可以忽略的,有些錯誤是可以修復的,其他錯誤則是沒救的
try: print(1 / 0) # 會被抓住 [].index(0) # 不會被抓住 except ZeroDivisionError: print('cannot divide by 0')
當然你可以接住多個特定的 exception
try:
你的 code
except 你想處理的 Exception:
你想處理的方法
except 你想處理的另一個 Exception:
你想處理的方法
所以只接住特定 exception 可以幹麻?
def get_int_list_from_input(): input_str = input() int_list = [] for n in input_str.split(): try: int_list.append(int(n)) except ValueError: pass # 我不在乎不能被轉成 int 的數值 return int_list
try: 你的 code except 處理其中一種 Exception: 處理的 code finally: 無論如何都會跑的 code
問題:為什麼需要 finally?
try: 你的 code except 處理其中一種 Exception: 處理的 code 無論如何都會跑的 code
誰說只有 Python 可以對我噴錯誤
raise 你想噴的 Exception
x = int(input()) if x == 0: raise Exception("不可以是 0!")
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
Exception: 不可以是 0!
當然也可以噴特定類型的錯誤
x = int(input()) if x == 0: raise ValueError("不可以是 0!")
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ValueError: 不可以是 0!
所以 raise
可以幹麻
def mean(list_): if type(list_) is not list: raise TypeError("This is not a list. Please pass a list.") if len(list_) == 0: raise ValueError("No average for an empty list.") for i in list_: if type(i) is not int: raise ValueError("Non-int value exists.") return sum(list_) / len(list_)
>>> mean(123)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in mean
TypeError: This is not a list. Please pass a list.
>>> mean([1, 2, 3, "asd"])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in mean
ValueError: Non-int value exists.
>>> mean([])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in mean
ValueError: No average for an empty list.
和你合作的工程師(以及未來的你自己)會更容易知道問題出在哪
我們來看看下面的 code 為什麼是不好的 / 錯的
list = list(map(int, input().split())) print(sum(list))
try: l = ["a", "b"] int(l[2]) except ValueError, IndexError: # 兩個錯誤都會被抓到,對吧 pass
counter = 0 def func(): counter += 1 print(f"Hello for the {counter}th time.") func() func() func()
students = ["Alice", "Bob", "", "Carol"] for i in range(len(students)): if students[i] == "": students = students[0:i] + students[i+1:len(students)] else: print(students[i])
def score_pass(a): if a > 60: return True else: return False
def complex_function(a, b, c): if not a: return None if not b: return None # Some complex code trying to compute x from a, b and c return x
所以回傳 None 時是 x 為 None 還是 a, b 有錯?
恭喜你從「會寫 code」成長到「會寫好 code」了