Exceptions ในภาษา Python

30 March 2018

ในบทนี้ คุณจะได้เรียนรู้เกี่ยวกับการจัดการข้อผิดพลาด (Error) ในภาษา Python ที่เรียกว่า Exception ซึ่งสามารถเกิดขึ้นได้ถึงแม้ว่า syntax ของโปรแกรมถูกต้อง แต่บางคำสั่งในโค้ดโปรแกรมนั้นทำให้เกิดข้อผิดพลาดขึ้น ซึ่งการจัดกการข้อผิดพลาดนั้นเป็นสิ่งที่ควรทำในการเขียนโปรแกรม เพราะมันจะทำให้โปรแกรมของคุณไม่แสดงข้อผิดพลาดให้กับผู้ใช้ได้เห็น

Syntax Errors

เหมือนที่คุณได้เรียนรู้โครงสร้างของภาษา Python ในบทก่อนหน้าทั้งหมดมาแล้ว เมื่อคุณเขียนโปรแกรมไม่ถูกต้องตามหลักไวยากร์ของภาษาจะทำให้เกิดข้อผิดพลาดขึ้นขณะที่โปรแกรมได้ทำการตรวจสอบซอสโค้ดของคุณหรือเราเรียกว่า Syntax errors

if True
    print('Enter the if block')

ในตัวอย่าง เป็นโค้ดของโปรแกรมที่จะทำให้เกิด Syntax errors ขึ้น เพราะว่าในคำสั่ง if จะต้องมีเครื่องหมายโคลอน (:) หลังจากเงื่อนไขของมัน เมื่อคุณรันโปรแกรมจึงทำให้เกิดข้อผิดพลาดขึ้น ดังนั้นในกรณีเกิด Syntax errors เราจำเป็นต้องแก้ไขโค้ดของโปรแกรมให้ถูกต้องก่อนจึงจะสามารถรันโปรแกรมได้

  File "exception.py", line 1
    if True
          ^
SyntaxError: invalid syntax

นี่เป็นตัวอย่างผลลัพธ์ของข้อผิดพลาดที่เกิดขึ้นเมื่อคุณรันโค้ดดังกล่าว โดย Python จะแสดงชนิดของข้อผิดพลาดที่เกิดขึ้น ซึ่งประกอบไปด้วยชื่อไฟล์ บรรทัดที่เกิดข้อผิดพลาด และประเภทของข้อผิดพลาด ตามด้วยข้อความอธิบายสิ่งที่เกิดขึ้น

Exceptions

Exception นั้นแตกต่างจากข้อผิดพลาดก่อนหน้า มันสามารถเกิดขึ้นได้เมื่อโปรแกรมของคุณทำงานบางอย่าง เช่น การหารด้วยศูนย์ การใช้งานตัวแปรที่ไม่ได้ประกาศไว้ หรือการแปลงข้อมูลคนละประเภทกัน โดยพื้นฐานของภาษาแล้ว ข้อผิดพลาดส่วนมากไม่ได้รับการจัดการอัตโนมัติ ดังนั้น คุณจำเป็นต้องจัดการกับมันเอง (Exception handing) ต่อไปเป็นตัวอย่างของคำสั่งที่สามารถทำให้เกิด Exception ได้

print (10 / 0)

print (5 * money)

print (1 + '2')

ในตัวอย่าง เป็นชุดของคำสั่งที่จะทำให้เกิด Exception ขึ้นและโปรแกรมจะหยุดการทำงานในทันที เราได้ทำการรันโปรแกรมสามครั้ง ในคำสั่งแรกเป็นการหารตัวเลขด้วยศูนย์ คำสั่งต่อมาเป็นการใช้งานตัวแปรที่ไม่ได้ประกาศ money และคำสั่งสุดท้ายเป็นการใช้งาน operand + กับประเภทข้อมูลที่ไม่ถูกต้อง

Traceback (most recent call last):
  File "exception.py", line 1, in <module>
    print (10 / 0)
ZeroDivisionError: division by zero

Traceback (most recent call last):
  File "exception.py", line 3, in <module>
    print (5 * money)
NameError: name 'money' is not defined

Traceback (most recent call last):
  File "exception.py", line 5, in <module>
    print (1 + '2')
TypeError: unsupported operand type(s) for +: 'int' and 'str'

เมื่อเราได้รันโปรแกรม ในคำสั่งแรกการหารตัวเลขด้วยศูนย์จะทำให้เกิด ZeroDivisionError exception ขึ้น บรรทัดถัดไป การใช้ตัวแปรที่ไม่ได้ประกาศไว้จะทำให้เกิด NameError exception และบรรทัดสุดท้าย การใช้ operand ไม่ถูกต้องกับประเภทของข้อมูลทำให้เกิด TypeError ตามลำดับ นี่เป็นข้อผิดพลาดพื้นฐานที่จะเกิดขึ้นเมื่อคุณเขียนโปรแกรมแล้วไม่ได้ตรวจสอบการทำงานให้ดี

Handling Exceptions

อย่างที่เราได้บอก เมื่อเกิด Exception ขึ้นโปรแกรมจะหยุดการทำงานในทันที ดังนั้นเพื่อให้โปรแกรมของเราสามารถทำงานต่อไปได้ เราจำเป็นต้องจัดการกับ Exception เหล่านั้น ซึ่งในภาษา Python มีรูปแบบในการจัดการกับ Exception ดังนี้

try:
    # do something

except firstError:
    # handing exception

except secondError as e:
    # handing exception

except:
    # handing exception

else:
    # excuted when no exception

ในการจัดการกับ Exception จะใช้คำสั่ง try ... except สำหรับตรวจจับข้อผิดพลาดที่จะเกิดขึ้น ในบล็อคของคำสั่ง try จะเป็นการทำงานที่จะทำให้เกิดข้อผิดพลาดขึ้น และเราสามารถมีบล็อคคำสั่ง except ได้หลายอันเพื่อจัดการข้อผิดพลาดประเภทต่างๆ และถ้าหากคุณไม่ได้กำหนดประเภทให้กับ except หมายความว่ามันสามารถจัดการกับข้อผิดพลาดได้ทุกประเภทที่สืบทอดมาจากคลาส Exception นอกจากนี้ คุณยังสามารถใช้ else clause ซึ่งจะทำงานเมื่อไม่เกิดข้อผิดพลาดขึ้นในขณะที่โปรแกรมทำงานในบล็อคคำสั่ง try ต่อไปมาดูตัวอย่างการจัดการข้อผิดพลาดในภาษา Python

try:
    a = int(input('Enter first number: '))
    b = int(input('Enter second number: '))
    print("%d / %d =  %f" % (a, b, a / b))

except ValueError as e:
    print ('You should enter a valid number')

except ZeroDivisionError as e:
    print ('Handing error: ', e)

ในตัวอย่าง เป็นโปรแกรมสำหรับรับค่าตัวเลขสองตัวจากทางคีย์บอร์ดและแสดงผลหาร ในบล็อคของคำสั่ง try เป็นการรับค่าตัวเลขและเก็บใส่ตัวแปร a และ b ตามลำดับ เนื่องต้องการแปลงค่าที่รับมาเป็นตัวเลขด้วยฟังก์ชัน int() ดังนั้น เราต้องมีบล็อค except เพื่อจัดการกับข้อผิดพลาด ValueError ที่จะเกิดขึ้นเมื่อค่าที่ใส่เข้ามานั้นไม่ได้เป็นตัวเลข

ต่อมาเป็นการแสดงผลการหารของตัวเลขเหล่านั้น ซึ่งสามารทำให้เกิด ZeroDivisionError exception ขึ้นได้เช่นกัน เราจึงได้ทำการใช้คำสั่ง except เพื่อจัดการกับข้อผิดพลาดนี้ ในการจัดการข้อผิดพลาดคุณอาจจะบอกวิธีการแก้ไขหรือแสดงรายละเอียดของข้อผิดพลาดนั้นให้ผู้ใช้โปรแกรมได้เห็นก็ได้

Enter first number: hello
You should enter a valid number

Enter first number: 10
Enter second number: 0
Handing error:  division by zero

Enter first number: 5
Enter second number: 3
5 / 3 =  1.666667

นี่เป็นผลลัพธ์เมื่อเราได้รันโปรแกรมเป็นจำนวนสามครั้ง ครั้งแรกเป็นการใส่ข้อมูลที่ไม่ใช่ตัวเลขเข้ามา ทำให้เกิดข้อผิดพลาดขึ้นและโปรแกรมทำงานในบล็อคคำสั่ง exception ของ ValueError ต่อมาเราได้ใส่ตัวเลขตัวที่สองเป็นศูนย์ ทำให้เกิดข้อผิดพลาด ZeroDivisionError ขึ้น และสุดท้ายโปรแกรมของเราทำงานได้โดยไม่มีข้อผิดพลาด

ต่อไปมาดูตัวอย่างเพิ่มเติมสำหรับการจัดการข้อผิดพลาดในการทำงานกับไฟล์ ซึ่งในการทำงานกับไฟล์นั้นมีข้อผิดพลาดต่างๆ ที่สามารถเกิดขึ้นได้ เช่น การเปิดไฟล์ที่ไม่มีอยู่ หรือไฟล์นั้นไม่พร้อมใช้งาน เป็นต้น

import sys

try:
    f = open('file.txt')
    s = f.readline()
    print(s)

except OSError as err:
    print("OS error: ", err)

except:
    print("Unexpected error occured")

else:
    print("File closed successfully")
    f.close()

ในการทำงานกับไฟล์ เราทำการนำเข้าไลบรารี่จาก sys มายังโปรแกรม และในบล็อคของคำสั่ง try เป็นการเปิดไฟล์ชื่อ file.txt และอ่านข้อมูลบรรทัดแรกมาแสดงผลทางหน้าจอ ในการเปิดไฟล์ถ้าหากไม่มีไฟล์อยู่จะทำให้เกิดข้อผิดพลาด OSError ขึ้นและเราได้ทำการแสดงข้อความบอกทางหน้าจอ เรายังทำการจัดการกับข้อผิดพลาดต่างๆ ที่อาจจะเกิดขึ้นด้วย

ในบล็อคของคำสั่ง else จะทำงานเมื่อไม่มีข้อผิดพลาดเกิดขึ้น นั้นหมายถึงเราเปิดไฟล์เพื่ออ่านข้อมูลได้สำเร็จ ดังนั้นเราจึงควรจะทำการปิดไฟล์ในบล็อคคำสั่งนี้

OS error:  [Errno 2] No such file or directory: 'file.txt'
marcuscode.com
File closed successfully

นี่เป็นผลลัพธ์ของโปรแกรมที่จะแสดงข้อผิดพลาดขึ้นเมื่อเรารันโปรแกรมในทันทีโดยที่ยังไม่มีไฟล์ file.txt อยู่ และถัดมาเป็นผลลัพธ์ของโปรแกรมเมื่อเราสร้างไฟล์ file.txtและภายในไฟล์มีข้อความ "marcuscode.com" อยู่ข้างใน ทำให้โปรแกรมสามารถอ่านไฟล์ได้และนำข้อความมาแสดงผลบนหน้าจอ และหลังจากนั้นโปรแกรมทำงานในบล็อคคำสั่ง else

Raising Exceptions

ในภาษา Python มี build-exception ที่จะเกิดขึ้นโดยพื้นฐานเมื่อโปรแกรมมีข้อผิดพลาดขึ้น อย่างไรก็ตามโปรแกรมเมอร์สามารถสั่งให้เกิด Exception ขึ้นเองได้ โดยการใช้คำสั่ง raise มาดูตัวอย่างการใช้งาน

try:
    name = input('Enter your name: ')
    if name == 'mateo':
        raise Exception('Whoa! Mateo you are not allowed here')

    print('Hi ', name)

except Exception as err:
    print("Exception: ", err)

else:
    print('Bye')

ในตัวอย่าง เป็นโปรแกรมสำหรับรับชื่อจากทางคีย์บอร์ดและทักทาย ถ้าหากชื่อที่ใส่เข้ามานั้นเป็น "mateo" เราจะทำให้เกิด exception ขึ้นด้วยคำสั่ง raise โดยสร้างออบเจ็คจากคลาส Exception ซึ่งเป็นคลาสในภาษา Python และกำหนดข้อความของเราเอง และถ้าหากชื่อที่ใส่เข้ามาเป็นอย่างอื่นที่ไม่ใช่ "mateo" โปรแกรมจะแสดงการทักทายและจบการทำงาน

Enter your name: mateo
Exception:  Whoa! Mateo you are not allowed here
Enter your name: Marcus
Hi  Marcus
Bye

และนี่เป็นผลลัพธ์การทำงานของโปรแกรม โดยครั้งแรกจะเกิดข้อผิดพลาดขึ้นเพราะว่าเราได้ใส่ชื่อเข้ามาเป็น "mateo" และครั้งที่สองไม่เกิดข้อผิดพลาดเพราะชื่อที่ใส่เข้ามาเป็น "marcus" และหลังจากนั้นโปรแกรมแสดงข้อความทักทายและคำบอกลาในบล็อคของคำสั่ง else

การสร้าง Exceptions

นอกจากการใช้งาน build-in exception จากภาษา Python แล้ว คุณยังสามารถสร้างคลาส Exception ขึ้นมาเองได้ เพื่อให้สามารถทำงานได้ตามที่ต้องการ ยกตัวอย่างเช่น การเพิ่มแอตทริบิวต์หรือเมธอดต่างๆ ภายในคลาส ต่อไปเราจะมาสร้างคลาสเพื่อจัดการข้อผิดพลาดของเราเอง โดยในการสร้างคลาสนั้นเราต้องทำการสืบทอดมาจากคลาส Exception เสมอ มาดูตัวอย่าง

# การสร้างคลาสซึ่งคุณอาจจะสร้างไว่ที่โมดูลอื่นเพื่อเรียกใช้งาน
class UsernameError(Exception):

    def __init__(self, message, error):
        super().__init__(message)
        self.message = message
        self.error = error

    def getMesssage(self):
        return self.message + ' \'' + self.error  + '\''

class PasswordError(Exception):

    def __init__(self, message, error):
        super().__init__(message)
        self.message = message
        self.error = error

    def getMesssage(self):
        return self.message + ' \'' + ('*' * len(self.error))  + '\''

# โปรแกรมเริ่มการทำงาน
try:
    print('Login')
    username = input('Username: ')
    password = input('Password: ')

    if (username != 'mateo'):
        raise UsernameError('Invalid username', username)

    if (password != '1234'):
        raise PasswordError('Invalid password', password)

    print('Login success')

except UsernameError as e:
    print('Exception: ', e.getMesssage())

except PasswordError as e:
    print('Exception: ', e.getMesssage())

ในตัวอย่างของโปรแกรมนั้นจะแบ่งออกเป็นสองส่วน ในส่วนแรกเป็นการสร้างคลาสโดยเราได้สร้างคลาสมาสองคลาสคือ UsernameError เป็นคลาสของ Exception สำหรับจัดการเมื่อ username ไม่ถูกต้อง และคลาส PasswordError เป็นคลาสของ Exception สำหรับจัดการข้อผิดพลาดเมื่อรหัสผ่านไม่ถูกต้อง โดยในคลาสเราได้กำหนดแอตทริบิวต์สองตัวคือ message เป็นความสำหรับอธิบายข้อผิดพลาด และ error เป็นข้อมูลที่เกิดข้อผิดพลาดขึ้น และภายในคลาสทั้งสองมีเมธอด getMesssage() สำหรับรับข้อความการแสดงข้อผิดพลาดที่แตกต่างกันออกไป

ในส่วนต่อมา เป็นการทดสอบการจัดการข้อผิดพลาดของเรา โดยการจำลองการทำงานระบบ Login สำหรับให้ผู้ใช้เข้าสู่ระบบโดยการใส่ username และ password โดยเราจะทำการตรวจสอบถ้าหากชื่อผู้ใช้ไม่เป็น "mateo" เราจะทำให้เกิดข้อผิดพลาด UsernameError ขึ้น แต่ถ้าชื่อผู้ใช้ถูกต้องแต่รหัสผ่านยังผิดจะทำให้เกิดข้อผิดพลาด PasswordError ขึ้น นอกจากนี้ หมายความว่าการเข้าสู่ระบบสำเร็จ

Login
Username: guest
Password: 1111
Exception:  Invalid username 'guest'
Login
Username: mateo
Password: 5555
Exception:  Invalid password '****'
Login
Username: mateo
Password: 1234
Login success

นี่เป็นผลลัพธ์การทำงานของโปรแกรมจากการรันสามครั้ง ครั้งแรกเราได้ใส่ชื่อผู้ใช้ที่ไม่ถูกต้อง และครั้งที่สองเราได้ใส่ชื่อผู้ใช้ถูกต้องแต่รหัสผ่านผิด คุณจะสังเกตุเห็นสิ่งที่แตกต่างคือการแสดงข้อผิดพลาดของรหัสผ่านจะถูกปกปิดไว้ เพราะเราเรียกใช้เมธอด getMesssage() ซึ่งมีการทำงานที่ไม่เหมือนกันสำหรับแต่ละคลาส และสุดท้ายเป็นการใส่ชื่อผู้ใช้และรหัสผ่านถูกต้อง โปรแกรมจะบอกว่าเข้าสู่ระบบสำเร็จ

การใช้คำสั่ง Finally

อีกคำสั่งหนึ่งที่จำเป็นสำหรับการจัดการข้อผิดพลาดก็คือคำสั่ง finally ที่สามารถใช้ร่วมกับคำสั่ง try ... except ได้ โดยการทำงานของมันนั้นจะแตกต่างจาก else คือจะทำงานในบล็อคคำสั่งนี้เสมอ ไม่ว่าจะเกิดข้อผิดพลาดหรือไม่ก็ตาม มาดูตัวอย่างการใช้งาน

try:
    items = ['Mac', 'iPhone', 'iPad']

    print('Avilable items: ', items)
    need = input('What do you want to buy?: ')
    if need not in items:
        raise Exception('Sorry, \'' + need + '\'' + ' out of stock')

    print('You have purchased ' + '\'' + need + '\'')

except Exception as e:
    print(e)

finally:
    print("Thank you for shopping with us")

ในตัวอย่าง เป็นโปรแกรมสำหรับสั่งชื้อสินค้าชนิด เราได้ประกาศสินค้าที่มีอยู่ใน Stock ใส่ไว้ในตัวแปรลิสต์ items โดยโปรแกรมจะถามว่าต้องการซื้ออะไร หลังจากที่ใส่ชื่อสินค้าเข้ามาแล้ว เราได้ทำการตรวจสอบว่ามีในลิสต์ items หรือไม่ ถ้าหากไม่มีจะแสดงข้อผิดพลาดขึ้นว่าสินค้าหมด และถ้าหากมี จะแสดงข้อความว่าได้ซื้อสำเร็จแล้ว และในตอนสุดท้ายเราได้แสดงข้อความขอบคุณไม่ว่าลูกค้าได้จะได้ซื้อหรือไม่ก็ตาม ดังนั้น เราจึงกำหนดให้มันทำงานในบล็อคคำสั่ง finally

Avilable items:  ['Mac', 'iPhone', 'iPad']
What do you want to buy?: xbox
Sorry, 'xbox' out of stock
Thank you for shopping with us
Avilable items:  ['Mac', 'iPhone', 'iPad']
What do you want to buy?: iPad
You have purchased 'iPad'
Thank you for shopping with us

นี่เป็นผลลัพธ์การทำงานของโปรแกรม อันแรกเป็นการใส่ชื่อสินค้าที่ไม่มีอยู่ ดังนั้นโปรแกรมจะแสดงว่าสินค้าหมดแล้ว และต่อมาเป็นการใส่ชื่อสินค่าที่มีอยู่คือ "iPad" และในตอนท้ายเราแสดงคำขอบคุณเสมอ

ในบทนี้ คุณได้เรียนรู้เกี่ยวกับการจัดการข้อผิดพลาดหรือ Exception ในภาษา Python เราได้ให้ตัวอย่างแบบต่างๆ ที่คุณสามารถนำไปประยุกต์เขียนโปรแกรมในขั้นสูงได้ต่อไป นอกจากนี้เรายังพูดเกี่ยวกับการสร้างคลาส Exception ขึ้นมาเองพร้อมวิธีการเรียกใช้งาน ซึ่งจะทำให้คุณสามารถขยายความสามารถในการจัดการข้อผิดพลาดตามที่คุณต้องการได้

บทความนี้เป็นประโยชน์หรือไม่? Yes · No