ทำไมเราถึงต้องการประโยค“ สุดท้าย” ใน Python?


306

ผมไม่แน่ใจว่าทำไมเราต้องfinallyอยู่ในtry...except...finallyงบ ในความคิดของฉันบล็อกรหัสนี้

try:
    run_code1()
except TypeError:
    run_code2()
other_code()

เหมือนกันกับสิ่งนี้โดยใช้finally:

try:
    run_code1()
except TypeError:
    run_code2()
finally:
    other_code()

ฉันพลาดอะไรไปรึเปล่า?

คำตอบ:


422

มันสร้างความแตกต่างถ้าคุณกลับก่อน:

try:
    run_code1()
except TypeError:
    run_code2()
    return None   # The finally block is run before the method returns
finally:
    other_code()

เปรียบเทียบกับสิ่งนี้:

try:
    run_code1()
except TypeError:
    run_code2()
    return None   

other_code()  # This doesn't get run if there's an exception.

สถานการณ์อื่น ๆ ที่อาจทำให้เกิดความแตกต่าง:

  • หากมีข้อยกเว้นเกิดขึ้นภายในบล็อกยกเว้น
  • หากยกเว้นจะโยนในแต่มันก็ไม่ได้เป็นrun_code1()TypeError
  • คำสั่งควบคุมการไหลอื่น ๆ เช่นcontinueและbreakคำสั่ง

1
ลอง: #x = สวัสดี +20 x = 10 + 20 ยกเว้น: พิมพ์ 'ฉันอยู่ในยกเว้นบล็อก' x = 20 + 30 อื่น: พิมพ์ 'ฉันอยู่ในบล็อกอื่น' x + = 1 ในที่สุด: พิมพ์ 'ในที่สุด x =% s '% (x)
Abhijit Sahu

89

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

myfile = open("test.txt", "w")

try:
    myfile.write("the Answer is: ")
    myfile.write(42)   # raises TypeError, which will be propagated to caller
finally:
    myfile.close()     # will be executed before TypeError is propagated

ในตัวอย่างนี้คุณควรใช้withคำสั่งดีกว่าแต่โครงสร้างชนิดนี้สามารถใช้กับทรัพยากรประเภทอื่นได้

ไม่กี่ปีต่อมาฉันเขียนบล็อกโพสต์เกี่ยวกับการละเมิดของfinallyผู้อ่านที่อาจพบว่าน่าขบขัน


23

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


15
Finally code is run no matter what else happens... เว้นแต่ว่าจะมีวง จำกัด หรือ powercut os._exit()หรือ หรือ ...
Mark Byers

3
@ Mark จริง ๆ แล้ว sys.exit ส่งข้อยกเว้นตามปกติ แต่ใช่สิ่งใดก็ตามที่ทำให้กระบวนการยุติในทันทีจะหมายถึงไม่มีสิ่งอื่นใดดำเนินการ
พลวง

1
@Antimony: ขอบคุณ os._exitเปลี่ยนไป
Mark Byers

เพิ่งสงสัยว่าทำไมถึงไม่สามารถวางโค้ดทำความสะอาดภายในยกเว้นว่าจะใส่รหัสยกเว้นเฉพาะเมื่อพบข้อยกเว้นเท่านั้น
สตีเฟ่นจาค็อบ

2
@Stephen สำหรับสิ่งหนึ่งในที่สุดรหัสทำงานแม้ว่าคุณจะกลับมาจากบล็อกลอง ในกรณีนี้คุณจะไม่ได้รับการยกเว้นข้อ
พลวง

18

หากต้องการเพิ่มคำตอบอื่น ๆ ข้างต้นfinallyประโยคจะดำเนินการไม่ว่าอะไรก็ตามในขณะที่elseประโยคนั้นจะทำงานก็ต่อเมื่อไม่มีข้อยกเว้นเกิดขึ้น

ตัวอย่างเช่นการเขียนไปยังไฟล์โดยไม่มีข้อยกเว้นจะส่งออกต่อไปนี้:

file = open('test.txt', 'w')

try:
    file.write("Testing.")
    print("Writing to file.")
except IOError:
    print("Could not write to file.")
else:
    print("Write successful.")
finally:
    file.close()
    print("File closed.")

เอาท์พุท:

Writing to file.
Write successful.
File closed.

หากมีข้อยกเว้นรหัสจะส่งออกต่อไปนี้ (โปรดทราบว่าข้อผิดพลาดโดยเจตนาเกิดจากการรักษาไฟล์อ่านอย่างเดียว

file = open('test.txt', 'r')

try:
    file.write("Testing.")
    print("Writing to file.")
except IOError:
    print("Could not write to file.")
else:
    print("Write successful.")
finally:
    file.close()
    print("File closed.")

เอาท์พุท:

Could not write to file.
File closed.

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


2
สิ่งนี้จะได้ผลแม้ว่าคุณจะไม่ได้ใช้ประโยค "สุดท้าย" ซึ่งไม่ตอบคำถามเนื่องจาก OP ต้องการทราบความแตกต่างตัวอย่างที่ดีจะทำให้เกิดข้อผิดพลาดที่แตกต่างจาก IOError เพื่อแสดงว่า ในที่สุดบล็อกข้อจะถูกดำเนินการก่อนที่ข้อยกเว้นจะแพร่กระจายไปยังผู้โทร
Reda Drissi

2
ฉันไม่รู้ว่าelseเป็นเรื่องอะไร มีประโยชน์น่ารู้
mazunki

8

บล็อคโค้ดไม่เท่ากัน finallyข้อนอกจากนี้ยังจะทำงานถ้าrun_code1()ผิดข้อยกเว้นอื่น ๆ กว่าTypeErrorหรือถ้าrun_code2()พ่นยกเว้นในขณะที่other_code()ในรุ่นแรกจะไม่ทำงานในกรณีเหล่านี้


7

ในตัวอย่างแรกของคุณ, เกิดอะไรขึ้นถ้าrun_code1()ยกข้อยกเว้นที่ไม่ได้เป็นTypeError? ... other_code()จะไม่ถูกดำเนินการ

เปรียบเทียบกับfinally:เวอร์ชัน: other_code()รับประกันว่าจะดำเนินการโดยไม่คำนึงถึงข้อยกเว้นใด ๆ


7

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

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

ตัวอย่าง:

>>> 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.0
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 <module>
  File "<stdin>", line 3, in divide
TypeError: unsupported operand type(s) for /: 'str' and 'str'

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

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


4

ตัวอย่างที่สมบูรณ์แบบดังต่อไปนี้:

try:
    #x = Hello + 20
    x = 10 + 20 
except:
    print 'I am in except block'
    x = 20 + 30
else:
    print 'I am in else block'
    x += 1
finally:
    print 'Finally x = %s' %(x)

3

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

ฉันสองตัวอย่างของ @ Byers


2

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

ในตัวอย่างต่อไปนี้เราไม่ทราบแน่ชัดว่ามีข้อยกเว้นประเภทใดstore_some_debug_infoบ้าง

เราสามารถเรียกใช้:

try:
  store_some_debug_info()
except Exception:
  pass
do_something_really_important() 

แต่ linters ส่วนใหญ่จะบ่นเกี่ยวกับข้อยกเว้นที่คลุมเครือเกินไป นอกจากนี้เนื่องจากเราเลือกเพียงpassข้อผิดพลาดexceptบล็อกจึงไม่เพิ่มมูลค่าจริงๆ

try:
  store_some_debug_info()
finally:
  do_something_really_important()     

รหัสข้างต้นมีผลเช่นเดียวกับรหัสที่ 1 แต่มีความกระชับมากกว่า


2

การใช้ delphi อย่างมืออาชีพเป็นเวลาหลายปีสอนให้ฉันป้องกันการทำความสะอาดตามปกติโดยใช้ในที่สุด Delphi ค่อนข้างบังคับใช้ในที่สุดเพื่อล้างทรัพยากรใด ๆ ที่สร้างขึ้นก่อนที่จะลองบล็อกเพื่อว่าคุณจะทำให้หน่วยความจำรั่ว นี่เป็นวิธีการทำงานของ Java, Python และ Ruby

resource = create_resource
try:
  use resource
finally:
  resource.cleanup

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

ทำไมคุณถึงต้องการบล็อกในที่สุดไม่ใช่ทุกภาษาที่ทำ ใน C ++ ที่คุณเรียกว่า destructors โดยอัตโนมัติซึ่งบังคับให้ล้างข้อมูลเมื่อข้อยกเว้นคลายกองซ้อน ฉันคิดว่านี่เป็นขั้นตอนในทิศทางของรหัสที่สะอาดกว่าเมื่อเปรียบเทียบกับลอง ... ในที่สุดภาษา

{    
  type object1;
  smart_pointer<type> object1(new type());
} // destructors are automagically called here in LIFO order so no finally required.

2

บล็อกการทดลองมีเพียงส่วนคำสั่งเดียวที่บังคับ: คำสั่งลอง ข้อยกเว้นส่วนอื่นและในที่สุดก็เป็นตัวเลือกและขึ้นอยู่กับการตั้งค่าของผู้ใช้

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


1
นี่เป็นสิ่งที่ผิด คำสั่งยกเว้นมีผลบังคับใช้ - Lucas Azevedo 1 ก.พ. เวลา 12:04 นี่เป็นสิ่งที่ผิดเนื่องจากฉันเพิ่งคอมไพล์และรันโปรแกรม Python 3.5 ด้วยการลองบล็อกในที่สุดโดยไม่มีประโยค "ยกเว้น"
Rob Tow

2
ฉันลองด้วยตัวเองและกับการไม่เชื่อของฉันข้อยกเว้นไม่ได้บังคับ
captainblack

1

เรียกใช้รหัส Python3 เหล่านี้เพื่อดูความต้องการในที่สุด:

CASE1:

count = 0
while True:
    count += 1
    if count > 3:
        break
    else:
        try:
            x = int(input("Enter your lock number here: "))

            if x == 586:
                print("Your lock has unlocked :)")

                break
            else:
                print("Try again!!")

                continue

        except:
            print("Invalid entry!!")
        finally:
            print("Your Attempts: {}".format(count))

CASE2:

count = 0

while True:
    count += 1
    if count > 3:
        break
    else:
        try:
            x = int(input("Enter your lock number here: "))

            if x == 586:
                print("Your lock has unlocked :)")

                break
            else:
                print("Try again!!")

                continue

        except:
            print("Invalid entry!!")

        print("Your Attempts: {}".format(count))

ลองอินพุตต่อไปนี้ในแต่ละครั้ง:

  1. จำนวนเต็มแบบสุ่ม
  2. รหัสที่ถูกต้องซึ่งเป็น 586 (ลองนี่และคุณจะได้รับคำตอบของคุณ)
  3. สตริงแบบสุ่ม

** ในช่วงแรก ๆ ของการเรียนรู้ Python


1

ฉันพยายามเรียกใช้รหัสที่ฉันต้องการอ่านแผ่นงาน Excel ปัญหาคือถ้ามีไฟล์ที่ไม่มีแผ่นงานชื่อว่า: SheetSum ฉันไม่สามารถย้ายไปยังตำแหน่งที่ผิดพลาดได้ !! รหัสที่ฉันเขียนคือ:

def read_file(data_file):
    # data_file = '\rr\ex.xlsx'
    sheets = {}
    try:
        print("Reading file: "+data_file)
        sheets['df_1'] = pd.read_excel(open(data_file,'rb'), 'SheetSum')
    except Exception as excpt:
        print("Exception occurred", exc_info=True)
    return sheets

read_file(file)
shutil.move( file, dirpath +'\\processed_files')

ให้ข้อผิดพลาด:

[WinError 32] กระบวนการไม่สามารถเข้าถึงไฟล์ได้เนื่องจากกำลังถูกใช้โดยกระบวนการอื่น

ฉันต้องเพิ่มtry except with finallyบล็อกเต็มและบอกfinallyฉันต้องปิดไฟล์ในกรณีใด ๆ เช่น:

def read_file(data_file):
    # data_file = '\rr\ex.xlsx'
    sheets = {}
    try:
        print("Reading file: "+data_file)
        sheets_file = open(data_file,'rb')
        sheets['df_1'] = pd.read_excel(sheets_file, 'SheetSum')
    except Exception as excpt:
        print("Exception occurred", exc_info=True)
    finally:
        sheets_file.close()
    return sheets

read_file(file)
shutil.move( file, dirpath +'\\processed_files')

มิฉะนั้นไฟล์ยังคงเปิดอยู่เป็นพื้นหลัง

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

.. ที่นี่มากขึ้น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.