# Coding Style & Error Handling 周楷翔 @ 資芽 2022 北區 Py 班 --- ## Coding Style - PEP 8 普通的工程師寫出電腦看得懂的程式,好的工程師寫出人看得懂的程式。 ---- ![](https://i.imgur.com/9ofp81v.png) ---- ![](https://i.imgur.com/SGsfrSd.png) ---- 為了確保人類容易看懂你的程式,大家約定了一系列的規則。 今天,我們會介紹 PEP8 - Style Guide for Python Code 完整規範:https://peps.python.org/pep-0008/ Be *Pythonic* ---- ### 關於 layout - indentation 永遠使用 4 個 space - 分行請再多一個 indent level 或對齊 delimiter ```python= # 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) ``` ---- 不要寫出這種東西 ```python= 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) ``` ---- 運算如果要換行,把運算符放在前面 (想想直式運算) ```python= income = (gross_wages + taxable_interest + (dividends - qualified_dividends) - ira_deduction - student_loan_interest) ``` ---- 如果一行太長了(原則上控制在 79 個字),可以用 `\` 換行 ```python= 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 前後都要空格 ```python= # 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) ``` ---- 拜託不要... ```python= # Wrong i=i+1 i+=1 x= x *2-1 c =(a +b ) *( a - b ) ``` ---- 不要在括號**裡面**放空格 ```python= # Correct: spam(ham[1], {eggs: 2}) foo = (0,) # Wrong: spam( ham[ 1 ], { eggs: 2 } ) bar = (0, ) ``` ---- 把空格放在逗點、冒號後面,而非前面 ```python= # Correct: if x == 4: print(x, y) # Wrong: if x == 4 : print(x , y) ``` ---- ### Naming Conventions 寫程式最難的部份:想出一個有意義的變數名稱 ---- ![](https://i.imgur.com/jhoHaWg.png) ---- - 不要用數字與英文[以外](https://www.reddit.com/r/ProgrammerHumor/comments/6l3u9i/recycling_old_meme/)的字元 - 確保名稱是有意義的 - `seen_values` 好於 `data` - `square(x)` 好於 `f(x)` - 一般的變數與 function name 用 `lowercase_with_underscore` - `get_average_score_from_list(score_list)` ---- - 如果變數名稱是保留字,可以在結尾加上 `_` - `class_`, `str_` - class name 用 `CapWords` ---- 如何取個好名字: - 可以嘗試把資料的意義與型態放進變數名稱:`username_list` - 如果是 boolean,可以嘗試用 `is_`, `has_` 開頭 - `is_reserved`, `has_stored` - function 可以用動詞開頭,並把重要的 argument 放進去 - `print_greeting_message()` vs. `get_greeting_message()` - `download_from_url(url)` ---- 迴圈裡面的命名:不要一直 `i` 啦 == ```python== 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) ``` ---- ### autopep8 ``` pip install autopep8 ``` ``` autopep8 --in-place my_code.py ``` ---- 遵守規範是一回事,寫出「好看的」程式是另一回事。 記得,普通的工程師寫出電腦看得懂的程式,好的工程師寫出人看得懂的程式。 最後,**規定是死,人是活的** 如果按照 PEP 8 讓你的 code 變得更難讀,請大膽地直接捨棄規則 --- ## Error Handling 程式執行總是會遇到一些奇怪的狀況,而你要想辦法處理這些程式會壞掉的情境 ``` >>> 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 ``` ---- 什麼叫做奇怪的狀況? - 該是 list 的地方變成 int 了 - 該是正數的地方變負數了 - 讀不到檔案、連不上網路(二階就會碰到了) - 電腦被宇宙射線打到 - 單純你的 code 爛到沒辦法執行 - 還有很多 ---- ![](https://i.imgur.com/Ecvq1A8.png) ---- 舉個例子: ```python= # 算平均 numbers = list(map(int, input().split(" "))) print(sum(numbers) / len(numbers)) ``` ---- 考慮以下幾個情境 ```python= input = "" # 空字串 input = "hello world" input = "1 2<tab>3" ``` ---- 有些錯誤是可以事先預防的(例如上面的空字串) 但有些錯誤遇到就是遇到了,沒辦法預防 ---- ### try except **嘗試**執行一段程式,然後**抓住例外** ```python= try: 你的 code except: 你的 code 爛掉就會跑來這裡 ``` ---- 舉個例子 ```python= try: [].index(1) except: print('1 not in list') ``` ```python= try: print(1 / 0) except: print('cannot divide by 0') ``` ---- 沒辦法接住 indentation error 跟 syntax error ```python= 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](https://docs.python.org/3/library/exceptions.html) 是以後才會碰到的 (例如讀檔時可能遇到 `OSError`) ---- 如何知道是什麼錯誤? ```python= try: print(1 / 0) except Exception as e: print(e, type(e)) # division by zero <class 'ZeroDivisionError'> ``` ---- 如何接住**特定**的錯誤 也許有些錯誤是可以忽略的,有些錯誤是可以修復的,其他錯誤則是沒救的 ```python= try: print(1 / 0) # 會被抓住 [].index(0) # 不會被抓住 except ZeroDivisionError: print('cannot divide by 0') ``` ---- 當然你可以接住多個特定的 exception ``` try: 你的 code except 你想處理的 Exception: 你想處理的方法 except 你想處理的另一個 Exception: 你想處理的方法 ``` ---- 所以只接住特定 exception 可以幹麻? ```python= 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-except-finally ```python= try: 你的 code except 處理其中一種 Exception: 處理的 code finally: 無論如何都會跑的 code ``` ---- 問題:為什麼需要 finally? ```python= try: 你的 code except 處理其中一種 Exception: 處理的 code 無論如何都會跑的 code ``` ---- ### raise 誰說只有 Python 可以對我噴錯誤 ```python= raise 你想噴的 Exception ``` ---- ```python= x = int(input()) if x == 0: raise Exception("不可以是 0!") ``` ``` Traceback (most recent call last): File "<stdin>", line 2, in <module> Exception: 不可以是 0! ``` ---- 當然也可以噴特定類型的錯誤 ```python= x = int(input()) if x == 0: raise ValueError("不可以是 0!") ``` ``` Traceback (most recent call last): File "<stdin>", line 2, in <module> ValueError: 不可以是 0! ``` ---- 所以 `raise` 可以幹麻 ```python= 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. ``` 和你合作的工程師(以及未來的你自己)會更容易知道問題出在哪 --- ## Common Mistakes 我們來看看下面的 code 為什麼是不好的 / 錯的 ---- ```python= list = list(map(int, input().split())) print(sum(list)) ``` ---- ```python= try: l = ["a", "b"] int(l[2]) except ValueError, IndexError: # 兩個錯誤都會被抓到,對吧 pass ``` ---- ```python= counter = 0 def func(): counter += 1 print(f"Hello for the {counter}th time.") func() func() func() ``` ---- ```python= 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]) ``` ---- ```python= def score_pass(a): if a > 60: return True else: return False ``` ---- ```python= 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 ``` <span>所以回傳 None 時是 x 為 None 還是 a, b 有錯?<!-- .element: class="fragment" data-fragment-index="2" --></span> --- 恭喜你從「會寫 code」成長到「會寫好 code」了
{"metaMigratedAt":"2023-06-16T23:22:17.562Z","metaMigratedFrom":"YAML","title":"Coding Style & Error Handling","breaks":true,"robots":"none","contributors":"[{\"id\":\"a30e4bd0-d7eb-41e2-898b-8571fad354d3\",\"add\":10297,\"del\":2186}]"}
    344 views