異常處理

當發生錯誤或異常時,Python通常會終止執行並產生錯誤訊息。為避免程式意外的終止執行,這些異常可以使用 try..except機制來處理:

In [26]:
# 因 xx 變數未定義,將生成異常
# 觸發 except:區塊的程式執行
try:
  print(xx)
except:
  print("異常")
異常

多重異常處理

In [27]:
# xx變數尚未定義, 
# 將生成一個 NameError異常
try:
  pxrint(xx)
except NameError:
  print("NameError異常:變數未定義")
except:
  print("其他異常")
NameError異常:變數未定義

else及finally

try..except機制還包含:

  • else:當無異常時執行
  • finally:不論有無異常都會執行
In [28]:
try:
  print("Hello")
except:
  print("異常")
else:
  print("無異常")
finally:
  print("finally一定會執行 ")
Hello
無異常
finally一定會執行 

自行抛出異常

Python 可以選擇在某些條件發生時,使用 raise 關鍵字拋出異常訊息,並透過 sys 模組的 exc_info()來取得異常訊息:

In [29]:
import sys

def 圓面積(半徑):
  # 當半徑為負值,丟出自訂異常
  if 半徑 < 0:
    raise Exception("半徑不可為負")
  else:
    return 3.14*半徑**2

try:
  圓面積(-1)
except:
  # 顯示異常訊息
  print(sys.exc_info()[1]) 
半徑不可為負

抽籤程式再改良版

在抽籤程式中,若輸入非數值或起始/結束值,將會造成程式執行錯誤,因此透過異常處理機制來避免程式意外停止執行:

In [30]:
import random

while True:
  try:
    人數 = int(input("請輸入抽獎人數:"))
    起始 = int(input("請輸入起始位置:"))
    結束 = int(input("請輸入結束位置:"))
  except:
    print("請輸入正確的整數")
  else:
    if 人數 < 0 or 起始 > 結束:
       print("人數必須為正數,起始值要小於結束值")
       continue
    break

候選清單=list(range(起始, 結束+1))

print("獲獎名單為:")
for i in range(人數):
  # 隨機抽籤
  中籤者=random.choice(候選清單)
  print(中籤者)

  # 移除已中籤者
  候選清單.remove(中籤者)
請輸入抽獎人數:5
請輸入起始位置:1
請輸入結束位置:6
獲獎名單為:
2
5
6
4
3

以上的範例還是有可能因異常造成程式意外停止,請你再進一步改良,避免任何的意外停止執行!

檔案管理

Python主要透過 open() 功能來處理檔案的管理,open() 函數帶有兩個參數:檔名和模式

開啓檔案有四種不同的模式:

  • "r":讀取(預設模式):打開檔案進行讀取,如果檔案不存在,則產生錯誤訊息
  • "a":附加:打開要附加的檔案,如果檔案不存在則創建該檔案
  • "w":寫入:打開檔案進行寫入,如果不存在則創建檔案
  • "x":創建:創建指定的檔案,如果檔案存在則返回錯誤訊息

另外可以指定文件應以二進制還是文字模式處理

  • "t":文字模式(預設模式)
  • "b":二進制模式(例如圖像)

讀取文字檔案

f = open("檔名") 或 f = open("檔名", "rt")

In [31]:
f = open("file/文字檔案.txt", "r")
print(f.read())
f.close()
這是範例檔案內容I
這是範例檔案內容II
這是新增的內容

如果檔案不存在,則會產生錯誤訊息:

f = open("file/不存在的檔案.txt", "r")
print(f.read())
f.close()
----> 1 f = open("file/不存在的檔案.txt", "r")
      2 print(f.read())
      3 f.close()

FileNotFoundError: [Errno 2] 
No such file or directory: 'file/不存在的檔案.txt'

逐行讀取文字檔案

In [32]:
# 讀取一行:
f = open("file/文字檔案.txt", "r")
print(f.readline())
f.close()
這是範例檔案內容I

In [33]:
# 逐行讀取文件:
f = open("file/文字檔案.txt", "r")
for x in f:
  print(x, end='')
f.close()
這是範例檔案內容I
這是範例檔案內容II
這是新增的內容

寫入檔案

要寫入現有檔案,必須設定 open() 函數的參數如下:

  • "a":將追加内容到檔案末尾
  • "w":將覆蓋現有檔案所有內容
In [34]:
# 寫入檔案
f = open("file/文字檔案.txt", "a")
f.write("\n這是新增的內容")
f.close()

# 讀取檔案
f = open("file/文字檔案.txt", "r")
print(f.read())
f.close()
這是範例檔案內容I
這是範例檔案內容II
這是新增的內容
這是新增的內容

創建新的檔案及刪除檔案

open() 的參數説明如下:

  • "x":創建新檔案,如果檔案存在則返回錯誤

  • "a":附加:如果檔案不存在,將創建一個新檔案

  • "w":覆蓋寫入:如果檔案不存在,將創建一新個檔案

In [35]:
# 創建新的檔案【執行第二次會出現 File exists 錯誤】
f = open("file/新檔案.txt", "x")

# 寫入一段文字
f.write("這是新檔案的內容")
f.close()

# 讀取檔案
f = open("file/新檔案.txt", "r")
print(f.read())
f.close()
這是新檔案的內容
In [36]:
import os

# 刪除檔案
os.remove("file/新檔案.txt")

檢查檔案是否存在

為避免出現錯誤,在嘗試刪除或建立新檔案之前檢查檔案是否存在:

In [37]:
import os

# 創建新的檔案前先檢查檔案是否存在
if os.path.exists("file/新檔案.txt"):
  # 覆蓋寫入舊有的檔案
  f = open("file/新檔案.txt", "w")  
else:
  # 創建新檔案
  f = open("file/新檔案.txt", "x")  

# 寫入一段文字
f.write("這是新檔案的內容")
f.close()

# 讀取檔案
f = open("file/新檔案.txt", "r")
print(f.read())
f.close()
這是新檔案的內容
In [38]:
import os

# 刪除檔案前先檢查檔案是否存在
if os.path.exists("file/新檔案.txt"):
  os.remove("file/新檔案.txt")
else:
  print("檔案不存在")

物件導向

物件導向基本概念

類別 (Class)

類別定義一件事物的抽象特點,包含了屬性(Field)以及對屬性的操作方法(Method)。我們可以將類別想像成汽車的設計藍圖,這張藍圖中會定義屬性(例如車子的顔色、驅動力等規格),另外會定義出車子的功能(方法),例如加速、刹車等。

物件 (Object)

物件是類別的實例,也就是說有了類別這張藍圖(汽車的設計藍圖),便可以在程式中產生許多同類別的實例(汽車),而這些實例彼此之間是各自獨立的。

類別:設計藍圖,決定汽車要怎麼製造,決定要用什麼規格的輪胎、多少排氣量的引擎、外觀要長怎樣。 物件:實際製造出來的汽車,可以各自獨立去執行不同任務。

物件導向具有三大特性:封裝、繼承、多型

封裝 (Encapsulation)

封裝是將物件內部的資料變成是一個黑箱子,只能透過物件所提供的介面(interface)取得內部的屬性或方法,物件設計的細節會被隱藏,其他物件無法也不需要瞭解物件內部的細節,要更動物件內部的資料只能透過物件提供之方法。例如電腦螢幕,我們只需要瞭解其介面的規格,並不需要知道螢幕内部的線路設計,若要更改螢幕的亮度,必須透過螢幕所提供的選單介面更改。

繼承 (Inheritance)

繼承者可以擁有被繼承者的特性。例如:繼承觸控螢幕具有一般螢幕的屬性及方法,也新增了特有的觸控屬性及方法。

多型 (Polymorphism)

Python不支援多載(Overloading)。

簡單來說就是多個相同名稱的方法,透過傳入不同形態的參數,會執行不同的敘述,稱之爲多型。多型(Polymorphism)包含多載(Overloading)和覆蓋(Overriding)。

  • 多載(Overloading):是指在同一類別中,定義名稱相同,但是參數不同的函式(包含個數或是型態不同),這樣便可以透過參數的個數或型態來分辨要呼叫那一對應的方法,如此便不需要針對不同的參數個數或型態來各自命名,在函式的使用上也會更加方便。例如:計算面積的方法,如果傳入一個參數,就計算正方形面積;傳入兩個參數,就計算長方形面積,而參數的型態可以是整數或浮點數。
  • 覆蓋(Overriding):簡單來説就是單父類別函式功能不能夠滿足所需時,子類別可以將父類別函式重新定義以符合所需。

而多型可以增加程式的彈性,讓相同的函式名稱在執行階段才決定要呼叫那一個函式,進而達「同名異式」的效果。

類別與物件

類別的定義包含屬性(Field)及方法(Method),Python 透過關鍵字class 創建類類,例如:

class myClass:
  x = 10

物件是類別的實例,例如:

In [39]:
class myClass:
  x = 10

myObj = myClass() #產生實例
print(myObj.x)    #列印實例的 x
10

物件的初始化

所有類別都有一個名為 __init __() 的初始化方法,該函數始終在建立物件時自動執行,一般會用在内部屬性的初始值設定,其中第一個參數代表物件本身,一般習慣命名為 self

In [40]:
class 學生資料:
  def __init__(self, 姓名, 身高, 體重):
    self.姓名 = 姓名
    self.身高 = 身高/100 #換算爲公尺
    self.體重 = 體重

新生1 = 學生資料("王小明", 170, 60)
print(新生1.姓名)
print(新生1.身高)
王小明
1.7

類別的方法

定義在類別内部的函數稱爲方法,其中第一個參數代表物件本身,一般習慣命名為self

In [41]:
class 學生資料:
  def __init__(self, 姓名, 身高, 體重):
    self.姓名 = 姓名
    self.身高 = 身高/100 #換算爲公尺
    self.體重 = 體重

  def BMI(self):
    # BMI=體重/身高的平方
    return self.體重/(self.身高**2)

新生1 = 學生資料("王小明", 170, 60)
print(新生1.BMI())

# 修改新生1的體重屬性
新生1.體重=80
print(新生1.BMI())
20.761245674740486
27.68166089965398

類別的繼承

類別可以定義繼承自另外一個類別的方法和屬性,被繼承的類別稱爲父類別,而繼承下來的類別為子類別。

In [42]:
# 家庭資料繼承自學生資料, 新增家長資料
class 家庭資料(學生資料):
  def __init__(self,家長姓名,姓名,身高,體重):
    # 呼叫父類別的初始化方法
    super().__init__(姓名, 身高, 體重)
    
    # 新增家長資料
    self.家長姓名 = 家長姓名 

新生1 = 家庭資料("王大明","王小明", 170, 60)
print(新生1.家長姓名)
print(新生1.姓名)
王大明
王小明

私有成員

再上述的例子中,類別外部的敘述都可以直接存取類別内部方法和屬性,然而實務上有些方法和屬性不應該被直接存取,應該限制在透過物件所提供的介面來存取,例如下一個例子當中,邊長不應當小於0,但因邊長可以直接被存取,因而無法避免邊長為負值:

In [43]:
class 正方形:
  邊長=1.0

  def 面積(self):
    return self.邊長**2

形狀=正方形() 
形狀.邊長=-10; # 邊長為負值
print(形狀.面積())
100

解決方式便是將半徑設爲私有成員,外部的敘述不能夠直接存取私有成員,只要在成員名稱加上兩個底線即可變成私有成員,直接讀取私有成員會產生錯誤訊息:

class 正方形:
  __邊長=1.0

  def 面積(self):
    return self.__邊長**2

形狀=正方形() 
print(形狀.__邊長) #無法讀取
      7 形狀=正方形()
----> 8 print(形狀.__邊長) #無法讀取

Attribute Error: '正方形' object has no attribute '__邊長'
屬性錯誤:‘正方形’ 沒有 ‘__邊長’ 屬性

下一個例子當中,因物件不知道私有成員,所以寫入私有成員時,反而會創建新的同名公有屬性

In [44]:
class 正方形:
  __邊長=1.0

  def 面積(self):
    return self.__邊長**2

形狀=正方形() 

#會創建新的同名公有屬性
形狀.__邊長=-10; 

print(形狀.__邊長)

#面積仍會以私有的 __邊長=1.0 計算
print(形狀.面積()) 
-10
1.0

總而言之,要讀取或寫入私有成員,皆應透過界面方法:

In [45]:
class 正方形:
  __邊長=1.0

  #取得邊長的界面方法
  def 取得邊長(self):
    return self.__邊長    
        
形狀=正方形()

print(形狀.取得邊長())  
1.0
In [46]:
class 正方形:
  __邊長=1.0

  def 面積(self):
    return self.__邊長**2

  # 設定邊長的界面方法
  def 設定邊長(self, 邊長):
    if 邊長 < 0:
        print("邊長不可為負")
    else:
        self.__邊長=邊長
        
形狀=正方形() 

形狀.設定邊長(-10); #設定會失敗
print(形狀.面積())  
邊長不可為負
1.0

上例中,雖然會顯示邊長不可為負值!的訊息,但 形狀.面積() 還是會被執行,透過 raise Exceptiontry..except 機制可以避免此錯誤:

In [47]:
import sys
class 正方形:
  __邊長=1.0

  def 面積(self):
    return self.__邊長**2

  # 設定邊長的界面方法
  def 設定邊長(self, 邊長):
    if 邊長 < 0:
      raise Exception("邊長不可為負")
    else:
      self.__邊長=邊長
        
形狀=正方形() 

try:
  形狀.設定邊長(-10); 
  print(形狀.面積())
except:
  print(sys.exc_info()[1])    
邊長不可為負

套件(Package)

套件會包含了一系列的模組,就像是資料夾内包含許多檔案。

第三方套件

第三方套件是需要額外安裝的套件,一般可以透過Python的pip套件管理工具來安裝及管理套件,請依序開啓下列選單,開啓[命令提示字元]視窗 [File]->[Open...]->右上角[new▾]->[Terminal]。 在[命令提示字元]視窗可輸入下列 pip 指令:

  • pip list:列出目前安裝的套件名稱及版本。
  • pip install 套件名稱:安裝套件
  • pip show 套件名稱:查詢該套件的詳細資料。
  • pip uninstall 套件名稱:解除安裝套件

例如下列指令會安裝 QR code 套件

pip install qrcode

Microsoft Azure Notebooks 請在code區塊中執行下列指令:

!pip install qrcode

Google 語音

Microsoft Azure Notebooks 請在code區塊中執行下列指令:

!pip install gtts
!pip install playsound
In [51]:
from gtts import gTTS
tts = gTTS("你好,我是谷歌小姐", lang='zh-tw')
tts.save("hello.mp3")
In [1]:
from gtts import gTTS
import playsound

msg = [
"你好,我是谷歌小姐",
"如果有什麼需要我唸的文字", 
"請把它放在這裡", 
"我會一句一句把它唸出來"
]

i=0; 
for m in msg:
    print(m)
    tts = gTTS(m, lang='zh-tw')
    tts.save("{}.mp3".format(i))
    playsound.playsound("{}.mp3".format(i))
    i+=1
你好,我是谷歌小姐
如果有什麼需要我唸的文字
請把它放在這裡
我會一句一句把它唸出來

QR Code 套件

安裝好qrcode套件後,執行下列程式碼,便會產生對應的QR Code:

  • Microsoft Azure Notebooks中 show()函數沒有作用。
  • 執行完後,可以透過 [File]->[Open]選單看到輸出的檔案。
In [3]:
import qrcode
im=qrcode.make("http://ewin.tw/python")
im.show()
im.save("myqrcode.jpg")

圖形套件 pillow

圖形套件 pillow廣汎支援各種常見圖檔格式,詳見官方網站説明。

安裝 pillow套件,請開啓[命令提示字元]視窗,執行下列指令安裝 pillow 套件

pip install pillow

Microsoft Azure Notebooks 可能會安裝失敗,可以改在code區塊中執行下列指令:

!pip install pillow

顯示圖片

In [ ]:
from PIL import Image

# 開啓圖片
im=Image.open("myqrcode.jpg")

# 顯示圖片
im.show()

# 另存圖片
im.save("output.jpg")

旋轉圖片

In [ ]:
from PIL import Image
im=Image.open("myqrcode.jpg")

# 圖片旋轉 45度
out=im.rotate(45) 
out.show()

out.save("output.jpg")

圖片濾鏡效果

In [55]:
from PIL import Image
from PIL import ImageFilter

im=Image.open("myqrcode.jpg")

# 輪廓濾鏡效果
out=im.filter(ImageFilter.CONTOUR)
out.show()

# 壓花濾鏡效果
out=im.filter(ImageFilter.EMBOSS)
out.show()
out.save("output.jpg")

改變圖片大小

In [ ]:
from PIL import Image

im=Image.open("myqrcode.jpg")

# 改變圖片大小,傳入一個 tuple
out=im.resize((100,100))
out.show()
out.save("output.jpg")

反轉圖片

In [ ]:
from PIL import Image, ImageOps

im=Image.open("myqrcode.jpg")

# 轉為灰階的圖片
im = im.convert('L')

# 反轉圖片
out = ImageOps.invert(im)
out.show()
out.save("output.jpg")

2D繪圖 Matplotlib

  • NumPy 是Python的一個擴充擴充模組,支援大量的維度陣列與矩陣運算,並提供大量陣列運算的數學函式庫。
  • Matplotlib 是Python的 2D繪圖擴充模組,可生成圖表、直方圖、功率譜、條形圖、誤差圖及散點圖等。

下面範例將繪製 $y=x^2$ 的曲線:

In [56]:
import numpy as np
import matplotlib.pyplot as plot

# y = x 的平方
x = np.arange(-10, 10, 0.1)
y = np.square(x)

# 繪製曲線
plot.plot(x, y)
plot.show()

下面範例將繪製 $y=sin(x), 0 \le x \le 2\pi $ 的曲線:

In [57]:
import numpy as np
import matplotlib.pyplot as plt

# y = sin(x)
x = np.arange(0, 2*np.pi, 0.1)
y = np.sin(x)

# 繪製曲線
ax=plt.plot(x, y)
plt.show()
In [58]:
import numpy as np
import matplotlib
import matplotlib.pyplot as plt

# y = sin(x)
x = np.arange(0, 2*np.pi, 0.1)
y = np.sin(x)

# 建立 subplots
fig, sp = plt.subplots()

# 建立網格及圖標
sp.grid()
sp.set(xlabel='x', ylabel='y',
       title='y=sin(x)')

# 繪製曲線
sp.plot(x, y)
plt.show()

# 匯出圖檔

fig.savefig("sin.png")

下面範例將繪製20個0~100的亂數分佈長條圖:

In [3]:
# 請先執行下列指令

# 在 JUPYTER NOTEBOOK 直接顯示圖形
%matplotlib inline

# 解決中文無法顯示
plt.rcParams['font.sans-serif'] = ['DFKai-sb'] 

# 讓字體變得清晰
%config InlineBackend.figure_format = 'retina'
In [4]:
import random
import matplotlib.pyplot as plt

# 產生 20 個 0~100 的亂數
亂數 = random.sample(range(0, 101), 20)
清單 = list(range(0, 101, 10))
plt.hist(亂數, 清單, histtype = "bar")
plt.xlabel("範圍")
plt.ylabel("亂數")
Out[4]:
Text(0, 0.5, '亂數')

下面範例將繪製4個數量的占比原形圖:

  • labels:扇形的標籤,預設為無。
  • explode:設定扇形是否分離,預設為無。
  • shadow:扇形下方是否顯示陰影,預設為無。
  • autopct: 設定扇形顯示的比例格式,%.1f%%表示顯示1位小數。
In [6]:
import matplotlib.pyplot as plt

項目= ["A", "B", "C", "D"]
數量 = [36, 36, 10, 3]
顔色 = ["green", "lightblue", "yellow", "orange"]

plt.pie(數量, labels=項目, colors=顔色, \
        shadow=True,explode=(0, 0, 0.2, 0), \
        autopct = "%.1f%%")
plt.axis("equal")
plt.show()

下面範例將繪製地理投影圖

In [7]:
import matplotlib.pyplot as plt

plt.figure()
plt.subplot(111, projection="aitoff")
plt.title("艾托夫投影")
plt.grid(True)
In [8]:
import matplotlib.pyplot as plt

plt.figure()
plt.subplot(111, projection="lambert")
plt.title("蘭伯特方位角等面積投影")
plt.grid(True)

下面範例將繪製 $Z=X^2+Y^2 $ 的3D曲面:

In [87]:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

X = np.arange(-5, 5, 0.25)
Y = np.arange(-5, 5, 0.25)

# 建立 X 與 Y 矩陣
X, Y = np.meshgrid(X, Y)

# Z = X² + Y²
Z = X**2 + Y**2

# 建立 3D 繪製物件
ax = Axes3D(plt.figure())

# 繪製曲面
ax.plot_surface(X, Y, Z, rstride=1, cstride=1)

plt.show()
In [103]:
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt

X = np.arange(-5, 5, 0.25)
Y = np.arange(-5, 5, 0.25)

# 建立 X 與 Y 矩陣
X, Y = np.meshgrid(X, Y)

# Z = X² + Y²
Z = X**2 + Y**2

# 建立 3D 繪製物件
ax = plt.figure().add_subplot(111, projection='3d')

# 繪製基本線框
ax.plot_wireframe(X, Y, Z, rstride=1, cstride=1)

plt.show()
🏠