φυβλαςのβλογ
บล็อกของ phyblas



การใช้ finally ร่วมกับ try และ except ในการจัดการข้อยกเว้นใน python
เขียนเมื่อ 2024/02/14 20:05
แก้ไขล่าสุด 2024/02/16 19:50
 




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




ความเข้าใจเบื้องต้น

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

โครงสร้าง try, except พร้อม finally อาจเขียนได้ดังนี้
try:
    คำสั่งที่กังวลว่าจะเกิดข้อยกเว้นขึ้นได้
except:
    คำสั่งสำหรับจัดการกับข้อยกเว้น
finally:
    คำสั่งที่จะให้ทำงานเสมอตอนท้ายที่สุด

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

ตัวอย่างให้พอเห็นภาพ

try:
    print('เดินไปโรงเรียน')
    raise Exception('หกล้มหัวคะมำ')
except Exception as e:
    print(e)
    print('ลุกขึ้นมาแล้วเดินต่อ')
finally:
    print('ในที่สุดก็ถึงโรงเรียน')
print('ตั้งใจเรียน')

ผลที่ได้
เดินไปโรงเรียน
หกล้มหัวคะมำ
ลุกขึ้นมาแล้วเดินต่อ
ในที่สุดก็ถึงโรงเรียน
ตั้งใจเรียน

ในที่นี้ทั้งส่วนของ print('ในที่สุดก็ถึงโรงเรียน') และ print('ตั้งใจเรียน') ก็ทำตอนท้ายไม่ว่าจะเกิดข้อยกเว้นหรือไม่ก็ตาม

ดังนั้นจะเห็นได้ว่าการใช้ finally ในกรณีแบบนี้ไม่ได้มีความจำเป็น เพราะสามารถเขียนข้างนอกได้

อย่างไรก็ตาม ความแตกต่างระหว่างการเขียนภายใน finally กับการเขียนข้างนอกจะเกิดขึ้นได้ในหลายกรณีด้วยกัน ในกรณีแบบนั้นจึงจะมีความหมายที่จะใช้ finally ซึ่งจะยกตัวอย่างต่อไปนี้



finally จะทำงานแม้เกิดข้อยกเว้นขึ้นอีกใน except

ดังที่ได้กล่าวไปว่าบทบาทของ finally ก็คือใช้ใส่คำสั่งที่ยังไงก็ต้องการให้ทำตอนท้ายสุดอย่างแน่นอนไม่ว่าจะเกิดอะไรขึ้นก็ตาม

ซึ่งยังรวมถึงกรณีที่เกิดข้อยกเว้นขึ้นซ้ำซ้อนภายในโครงสร้าง except ด้วย

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

แต่ว่าถ้าหากเขียนเอาไว้ใน finally แล้วละก็ คำสั่งนั้นจะทำงานขึ้นมาก่อนที่โปรแกรมจะหยุดทำงานไปจริงๆ

ตัวอย่าง
try:
    raise Exception
except:
    raise Exception
finally:
    print('ส่วนนี้จะทำงาน')
print('ส่วนนี้ไม่ทำงาน')

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

ดังนั้นแล้วถ้ามีคำสั่งสำคัญที่ต้องการทำตอนท้ายเสมอละก็อาจใส่ใน finally แบบนี้ก็จะมั่นใจได้ว่าจะทำงานจริงๆเสมอ



การใช้ try finally โดยละ except

อีกกรณีที่อาจใช้ finally ก็คือเมื่อจะใช้ try โดยไม่ใช่ except

ปกติแล้ว try จะใช้กับ except เสมอ ไม่สามารถเขียนเดี่ยวๆได้ แม้ว่าจะไม่ได้มีเนื้อหาอะไรที่ต้องการให้จัดการเมื่อเจอข้อยกเว้นก็ตาม

แต่ว่านอกจากนี้แล้ว try ยังใช้คู่กับ finally เฉยๆได้ด้วย โดยที่ไม่มี except แบบนี้ก็จะกลายเป็นโครงสร้าง try finally

เพียงแต่ว่าถ้าไม่มี except ละก็ ข้อยกเว้นที่เกิดขึ้นใน try ก็จะไม่ถูกยกเลิกและโปรแกรมก็จะหยุดการทำงาน

ดังนั้นความหมายของการใช้โครงสร้าง try finally ก็คือเมื่อมีคำสั่งบางอย่างที่ต้องการให้ทำแม้ว่าจะมีข้อยกเว้นใน try แต่ว่าไม่จำเป็นต้องหักล้างข้อยกเว้นนั้น แต่ให้โปรแกรมหยุดทำงานไป

ซึ่งถ้าเขียนด้วยโครงสร้าง try except ละก็อาจเขียนในลักษณะนี้
try:
    # คำสั่งบางอย่าง
except Exception as e:
    # คำสั่งที่ต้องการให้ทำเมื่อเกิดข้อยกเว้นใน try ก่อนจะหยุดการทำงาน
    raise e # ปล่อยให้เกิดข้อยกเว้นแล้วหยุดการทำงานไป

แต่ถ้าเชียนใหม่โดยใช้ try finally ก็จะเขียนได้แบบนี้ ซึ่งสั้นลง
try:
    # คำสั่งบางอย่าง
finally:
    # คำสั่งที่ต้องการให้ทำเมื่อเกิดข้อยกเว้นใน try ก่อนจะหยุดการทำงาน

ตัวอย่างเช่น
def a_han_b(a,b):
    try:
        print(a/b)
    finally:
        print('หารเสร็จแล้ว')

a_han_b(3,0)

แบบนี้จะมีข้อความ หารเสร็จแล้ว ปรากฎขึ้นมาก่อนที่จะตามมาด้วยข้อความประกาศว่ามีข้อยกเว้นเกิดขึ้น



กรณีที่มีการ return ใน finally ภายในฟังก์ชัน

ถึงจะเพิ่งบอกไปว่า finally นั้นไม่ได้มีส่วนช่วยในการลบล้างข้อยกเว้นที่เกิดขึ้น ถึงคำสั่งในโครงสร้าง finally จะทำงานก่อนเกิดข้อยกเว้น แต่ยังไงหลังจากนั้นโปรแกรมก็จะหยุดลง แต่อย่างไรก็ตาม ถ้าหาก finally ถูกใช้ภายในโครงสร้างฟังก์ชัน มันสามารถทำให้ข้อยกเว้นที่เกิดขึ้นถูกลบล้างได้จริงๆ โดยการใส่ return ในนั้น เช่น
def f():
    try:
        raise Exception
    finally:
        return

f()

หรือกรณีที่มีข้อยกเว้นขึ้นใน except อีกก็ด้วย
def f():
    try:
        raise Exception
    except:
        raise Exception
    finally:
        return

f()

กรณีแบบนี้แมว่าจะมีการโยนข้อยกเว้นลงใน try แต่เพราะมี return อยู่ใน finally ทำให้ข้อยกเว้นถูกลบล้างหายไปและโปรแกรมก็ทำงานไปได้ตามปกติ

หมายความว่าถ้าภายในฟังก์ชันมีการใส่ return ลงไปใน finally ก็จะไม่หยุดการทำงานกลางคันไม่ว่าจะเกิดข้อยกเว้นในโครงสร้าง try หรือ except ก็ตาม



แม้มีการ return ใน try หรือ except

ปกติแล้วเมื่อมีการใช้คำสั่ง return ภายในฟังก์ชันขึ้นมา การทำงานในฟังก์ชั้นนั้นจะสิ้นสุดลงทันที

แต่กรณีที่ใช้โครงสร้าง try finally ในฟังก์ชัน แล้วเกิดการ return ภายในโครงสร้าง try สิ่งที่เขียนไว้ใน finally ก็ยังจะทำงานด้วยตอนจบฟังก์ชัน
def f():
    try:
        return
    finally:
        print('คำสั่งในส่วนนี้ทำงานก่อนจบฟังก์ชัน')

f()

ดังนั้นความหมายของ finally ที่ว่ามีใช้เพื่อยืนยันว่าจะทำงานอย่างแน่นอนตอนท้ายสุดนั้น ไม่ใช่แค่ใช้ในการจัดการข้อยกเว้นเท่านั้น แต่ยังใช้ในกรณีที่ไม่เกี่ยวกับข้อยกเว้นได้ด้วย คือใช้กับฟังก์ชันเมื่อมีคำสั่งที่ต้องการให้ทำงานเสมอตอนจบฟังก์ชัน

นอกจากนี้แล้ว ในกรณีที่มีการ return ภายใน except ก็เช่นกัน ยังไงคำสั่งในโครงสร้าง finally ก็ทำงาน
def f():
    try:
        raise Exception
    except:
        return
    finally:
        print('คำสั่งในส่วนนี้ทำงานก่อนจบฟังก์ชัน')

f()



ค่าที่ return ใน finally จึงถูกนำไปใช้

สุดท้ายนี้ น่าคิดดูว่าถ้าหากมีการ return เกิดขึ้นทั้งในโครงสร้าง try, except และ finally แล้วจะเป็นอย่างไร

ลองดูตัวอย่างนี้
def f(x):
    try:
        if(x == 0):
            return 'พยายาม'
        else:
            raise Exception
    except:
        return 'ยกเว้น'
    finally:
        return 'จบแล้ว'

print(f(0)) # จบแล้ว
print(f(1)) # จบแล้ว

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

เห็นแบบนี้แล้วรู้เลยว่า finally นั้นช่างแข็งแกร่ง

อย่างไรก็ตาม ผู้เชี่ยวชาญแนะนำว่าไม่ควรใส่ return ภายใน finally เพราะแม้ว่าจะไม่ผิดไวยากรณ์ แต่มักจะนำไปสู่ผลลัพธ์ที่ไม่ได้คาดหวัง ดังนั้นจึงแค่รู้ไว้เฉยๆก็พอว่าถ้าทำแบบนี้แล้วจะเกิดผลแบบนี้ขึ้นได้ แต่ว่าถ้าไม่จำเป็นก็ไม่น่าจะนำไปใช้จริงๆ



สรุปทิ้งท้าย

จากที่เขียนมานั้นคงจะมองเห็นภาพกันแล้วว่า finally นั้นมีความหมายยังไง ใช้งานยังไงบ้าง

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

แต่ว่าดังที่ได้อธิบายมาจะเห็นว่า finally เป็นอะไรที่มากกว่านั้น ดังนั้นถ้าจะให้ดีจริงๆควรรอธิบายเสริมในส่วนนี้ไปด้วย เช่นอาจอธิบายว่าเป็น "ส่วนที่จะทำงานเสมอในท้ายที่สุดแม้ว่าจะเกิดข้อยกเว้นหรือการ return ขึ้นใน try หรือ except"



อ้างอิง


-----------------------------------------

囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧

ดูสถิติของหน้านี้

หมวดหมู่

-- คอมพิวเตอร์ >> เขียนโปรแกรม >> python

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

สารบัญ

รวมคำแปลวลีเด็ดจากญี่ปุ่น
มอดูลต่างๆ
-- numpy
-- matplotlib

-- pandas
-- manim
-- opencv
-- pyqt
-- pytorch
การเรียนรู้ของเครื่อง
-- โครงข่าย
     ประสาทเทียม
ภาษา javascript
ภาษา mongol
ภาษาศาสตร์
maya
ความน่าจะเป็น
บันทึกในญี่ปุ่น
บันทึกในจีน
-- บันทึกในปักกิ่ง
-- บันทึกในฮ่องกง
-- บันทึกในมาเก๊า
บันทึกในไต้หวัน
บันทึกในยุโรปเหนือ
บันทึกในประเทศอื่นๆ
qiita
บทความอื่นๆ

บทความแบ่งตามหมวด



ติดตามอัปเดตของบล็อกได้ที่แฟนเพจ

  ค้นหาบทความ

  บทความแนะนำ

ตัวอักษรกรีกและเปรียบเทียบการใช้งานในภาษากรีกโบราณและกรีกสมัยใหม่
ที่มาของอักษรไทยและความเกี่ยวพันกับอักษรอื่นๆในตระกูลอักษรพราหมี
การสร้างแบบจำลองสามมิติเป็นไฟล์ .obj วิธีการอย่างง่ายที่ไม่ว่าใครก็ลองทำได้ทันที
รวมรายชื่อนักร้องเพลงกวางตุ้ง
ภาษาจีนแบ่งเป็นสำเนียงอะไรบ้าง มีความแตกต่างกันมากแค่ไหน
ทำความเข้าใจระบอบประชาธิปไตยจากประวัติศาสตร์ความเป็นมา
เรียนรู้วิธีการใช้ regular expression (regex)
การใช้ unix shell เบื้องต้น ใน linux และ mac
g ในภาษาญี่ปุ่นออกเสียง "ก" หรือ "ง" กันแน่
ทำความรู้จักกับปัญญาประดิษฐ์และการเรียนรู้ของเครื่อง
ค้นพบระบบดาวเคราะห์ ๘ ดวง เบื้องหลังความสำเร็จคือปัญญาประดิษฐ์ (AI)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ

บทความแต่ละเดือน

2024年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2023年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2022年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2021年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2020年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

ค้นบทความเก่ากว่านั้น

ไทย

日本語

中文