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
เราอาจสร้างชนิดสิ่งผิดปรกติเองโดยสร้างเป็นคลาส ซึ่งต้องผัน (derive) มาจากคลาส Exception ไม่ว่าจะโดยตรงหรือโดยอ้อม
>>> 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 ดังนี้
with open("myfile.txt") as f:
for line in f:
print line
ตัวอย่างนี้ ไฟล์ออบเจกต์ f จะถูกลบออกจากหน่วยความจำเมื่อโปรแกรมจบ