# 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}]"}