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 จะถูกลบออกจากหน่วยความจำเมื่อโปรแกรมจบ