8.1 ผิดพลาดทางโครงสร้างประโยค (Syntax Errors)
8.2 ข้อผิดพลาดตอนทำงาน (Exceptions)
8.3 การจัดการ (Handling Exceptions)
8.4 การยกข้อผิดพลาด (Raising Exceptions)
8.5 สร้างกับดักเอง (User-defined Exceptions)
8.6 ดักให้หมดจด (Defining Clean-up Actions)
8.7 ละเอียดนิดเพื่อความหมดจด (Predefined Clean-up Actions)
ข้อผิดพลาดมีสองแบบคือ ผิดทางโครงสร้างประโยค (syntax errors) หรือผิดตอนโปรแกรมทำงาน (exceptions)
มือใหม่ต้องเจอทุกคน
>>> while True print 'Hello world' File "<stdin>", line 1, in ? while True print 'Hello world' ^ SyntaxError: invalid syntax
ในที่นี้คือตก :
หลัง True
ข้อผิดพลาดแบบนี้ต้องตามไปแก้ในโค๊ดอย่างเดียว
เกิดตอนรัน
>>> 10 * (1/0) Traceback (most recent call last): File "<stdin>", line 1, in ? ZeroDivisionError: integer division or modulo by zero >>> 4 + spam*3 Traceback (most recent call last): File "<stdin>", line 1, in ? NameError: name 'spam' is not defined >>> '2' + 2 Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError: cannot concatenate 'str' and 'int' objects
บรรทัดสุดท้ายเป็นการบอกว่าผิดเรื่องอะไร
จากตัวอย่าง จะเห็นว่าผิดอยู่สามเรื่องคือ ZeroDivisionError
NameError
และ TypeError
สามารถดูรายละเอียดทั้งหมดได้ที่ module-exceptions
จัดการด้วยประโยค try: ... except[(ErrorType)]: ... [else: ...]
ตัวอย่างข้างล่างเป็นการจัดการข้อผิดพลาด โดยจะจัดการดูเฉพาะค่าที่เราป้อนเข้าไปว่าถูกต้องหรือไม่ (ValueError
) แต่ถ้าเป็นข้อผิดพลาดแบบอื่น โปรแกรมจะปล่อยให้เป็นหน้าที่ของระบบ จึงทำให้เราสามารถใช้ปุ่ม Ctrl-C
ในการตัดการทำงานของโปรแกรมได้ ซึ่งกรณีตัดการทำงานนี้จะเป็นข้อผิดพลาดแบบ KeyboardInterrupt
>>> while True: ... try: ... x = int(raw_input("Please enter a number: ")) ... break ... except ValueError: ... print "Oops! That was no valid number. Try again..." ...
เราระบุข้อผิดพลาดได้หลายแบบในครั้งเดียว
... except (RuntimeError, TypeError, NameError): ... pass
หรืออาจดักเป็นขั้น ๆ จนในที่สุดไม่ใส่อะไรเลย คือ ดักทุกอย่างที่เหลือ
import sys try: f = open('myfile.txt') s = f.readline() i = int(s.strip()) except IOError, (errno, strerror): print "I/O error(%s): %s" % (errno, strerror) except ValueError: print "Could not convert data to an integer." except: print "Unexpected error:", sys.exc_info()[0] raise
อาจใช้ร่วมกับ else:
ในกรณีที่ try:
ไม่ยอมแจ้งข้อผิดพลาด
for arg in sys.argv[1:]: try: f = open(arg, 'r') except IOError: print 'cannot open', arg else: print arg, 'has', len(f.readlines()), 'lines' f.close()
ถ้าเติมตัวแปร(อาจเป็นทูเปิล)หลัง Exception
ตัวแปรนั้นจะเก็บค่าอินสแตนซ์ของการดัก
>>> try: ... raise Exception('spam', 'eggs') ... except Exception, inst: ... print type(inst) # the exception instance ... print inst.args # arguments stored in .args ... print inst # __str__ allows args to printed directly ... x, y = inst # __getitem__ allows args to be unpacked directly ... print 'x =', x ... print 'y =', y ... <type 'instance'> ('spam', 'eggs') ('spam', 'eggs') x = spam y = eggs
การดักข้อผิดพลาดนี้ ไม่เพียงแต่ดักเฉพาะบล๊อกนั้น แต่สามารถย้อนขึ้นไปถึงต้นตอได้
>>> def this_fails(): ... x = 1/0 ... >>> try: ... this_fails() ... except ZeroDivisionError, detail: ... print 'Handling run-time error:', detail ... Handling run-time error: integer division or modulo by zero
เราสามารถยกข้อผิดพลาดมาแสดงได้ เช่น
>>> raise NameError, 'HiThere' Traceback (most recent call last): File "<stdin>", line 1, in ? NameError: HiThere
พารามิเตอร์ตัวแรกเป็นชื่อข้อผิดพลาด อันหลังเป็นข้อความการผิดพลาด ซึ่งอาจเขียนอีกรูปนึงว่า raise NameError('HiThere')
สามารถใช้คำสั่ง raise
เพื่อยกข้อผิดพลาดขึ้นอีกครั้ง ภายในบล๊อกที่ดักความผิดพลาดได้
>>> try: ... raise NameError, 'HiThere' ... except NameError: ... print 'An exception flew by!' ... raise ... An exception flew by! Traceback (most recent call last): File "<stdin>", line 2, in ? NameError: HiThere
เราอาจสร้างกับดักเองโดยสร้างเป็นคลาส ซึ่งเวลานำมาใช้ต้องผันมาจากคลาสที่สร้างไว้อีกที
>>> class MyError(Exception): ... def __init__(self, value): ... self.value = value ... def __str__(self): ... return repr(self.value) ... >>> try: ... raise MyError(2*2) ... except MyError, e: ... print 'My exception occurred, value:', e.value ... My exception occurred, value: 4 >>> raise MyError, 'oops!' Traceback (most recent call last): File "<stdin>", line 1, in ? __main__.MyError: 'oops!'
จากตัวอย่างนี้ เมธอด __init__
ของคลาสเริ่มคือ Exception
ถูกครอบด้วยโค๊ดของเรา
หากมอดูลเราต้องมีการดักข้อผิดพลาดหลายอย่าง เราควรสร้างคลาสที่ใช้เป็นฐานก่อน แล้วจึงผันจากคลาสฐานของเราอีกทีนึง เวลาเปลี่ยนแปลงอะไรจะทำได้ง่ายกว่า คือทำที่คลาสฐานอันเดียว
class Error(Exception): """Base class for exceptions in this module.""" pass class InputError(Error): """Exception raised for errors in the input. Attributes: expression -- input expression in which the error occurred message -- explanation of the error """ def __init__(self, expression, message): self.expression = expression self.message = message class TransitionError(Error): """Raised when an operation attempts a state transition that's not allowed. Attributes: previous -- state at beginning of transition next -- attempted new state message -- explanation of why the specific transition is not allowed """ def __init__(self, previous, next, message): self.previous = previous self.next = next self.message = message
ดูเรื่องคลาสได้ในบทต่อไป
ในประโยค try:
จะมีวลีเผื่อเลือกอีกตัวคือ finally:
โค๊ดที่อยู่ในบล๊อกนี้จะถูกรันเสมอ ไม่ว่าจะเกิดข้อผิดพลาดขึ้นหรือไม่
>>> try: ... raise KeyboardInterrupt ... finally: ... print 'Goodbye, world!' ... Goodbye, world! Traceback (most recent call last): File "<stdin>", line 2, in ? KeyboardInterrupt
ประโยชน์ของวลี finally:
คือ ถ้าผ่านบล๊อกของ except:
และ else:
มาแล้ว ถ้ายังไม่แจ้งข้อผิดพลาด จะถูกแจ้งในบล๊อกนี้ และไม่ว่าจะพบคำสั่ง break
continue
หรือ return
ก็ตาม โค๊ดในส่วนนี้ก็ยังจะถูกรันเสมอ
>>> def divide(x, y): ... try: ... result = x / y ... except ZeroDivisionError: ... print "division by zero!" ... else: ... print "result is", result ... finally: ... print "executing finally clause" ... >>> divide(2, 1) result is 2 executing finally clause >>> divide(2, 0) division by zero! executing finally clause >>> divide("2", "1") executing finally clause Traceback (most recent call last): File "<stdin>", line 1, in ? File "<stdin>", line 3, in divide TypeError: unsupported operand type(s) for /: 'str' and 'str'
ควรมีความรอบคอบในการเขียนโค๊ด ลองดู
for line in open("myfile.txt"): print line
ปัญหาของโค๊ดนี้คือ ไม่มีการปิดไฟล์ ถ้าโปรแกรมใหญ่ขึ้นโค๊ดแบบนี้จะกินหน่วยความจำมหาศาล
ดังนั้นโค๊ดควรเป็นดังนี้
with open("myfile.txt") as f: for line in f: print line
ตัวอย่างนี้ ไฟล์ออปเจคต์ f
จะถูกลบออกจากหน่วยความจำเมื่อโปรแกรมจบ