Tại sao chúng ta cần mệnh đề cuối cùng của Viking trong Python?


306

Tôi không chắc tại sao chúng ta cần finallytrong các try...except...finallytuyên bố. Theo tôi, khối mã này

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

tương tự với cái này khi sử dụng finally:

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

Tui bỏ lỡ điều gì vậy?

Câu trả lời:


422

Nó làm cho một sự khác biệt nếu bạn trở về sớm:

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

So sánh với điều này:

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

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

Các tình huống khác có thể gây ra sự khác biệt:

  • Nếu một ngoại lệ được ném vào bên trong khối ngoại trừ.
  • Nếu một ngoại lệ được ném vào run_code1()nhưng nó không phải là a TypeError.
  • Báo cáo lưu lượng kiểm soát khác như continuebreakbáo cáo.

1
thử: s '% (x)
Abhijit Sahu

89

Bạn có thể sử dụng finallyđể đảm bảo các tệp hoặc tài nguyên được đóng hoặc phát hành bất kể ngoại lệ có xảy ra hay không, ngay cả khi bạn không bắt ngoại lệ. (Hoặc nếu bạn không bắt được ngoại lệ cụ thể đó .)

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

Trong ví dụ này, bạn nên sử dụng withcâu lệnh tốt hơn , nhưng loại cấu trúc này có thể được sử dụng cho các loại tài nguyên khác.

Vài năm sau, tôi đã viết một bài đăng trên blog về việc lạm dụng finallymà độc giả có thể thấy thú vị.


23

Chúng không tương đương. Cuối cùng mã được chạy bất kể điều gì khác xảy ra. Nó rất hữu ích cho mã dọn dẹp phải chạy.


15
Finally code is run no matter what else happens... Trừ khi có một vòng lặp vô hạn. Hoặc một powercut. Hoặc os._exit(). Hoặc ...
Mark Byers

3
@Mark Trên thực tế, sys.exit ném một ngoại lệ bình thường. Nhưng có, bất cứ điều gì khiến quá trình chấm dứt ngay lập tức sẽ có nghĩa là không có gì khác thực thi.
Antimon

1
@Antimony: Cảm ơn. Thay đổi thành os._exit.
Mark Byers

Chỉ cần tự hỏi, tại sao không thể đặt mã dọn dẹp trong ngoại trừ nếu mã sẽ nhập ngoại trừ chỉ khi tìm thấy một ngoại lệ?
Stephen Jacob

2
@Stephen Vì một điều, cuối cùng mã vẫn chạy ngay cả khi bạn quay lại từ khối thử. Trong trường hợp đó, bạn sẽ không nhấn vào điều khoản ngoại trừ.
Antimon

18

Để thêm vào các câu trả lời khác ở trên, finallymệnh đề thực thi bất kể điều gì trong khi elsemệnh đề chỉ thực thi nếu một ngoại lệ không được nêu ra.

Ví dụ: ghi vào một tệp không có ngoại lệ sẽ xuất ra như sau:

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.")

ĐẦU RA:

Writing to file.
Write successful.
File closed.

Nếu có một ngoại lệ, mã sẽ xuất ra các phần sau, (lưu ý rằng lỗi cố ý là do giữ tệp chỉ đọc.

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.")

ĐẦU RA:

Could not write to file.
File closed.

Chúng ta có thể thấy rằng finallymệnh đề thực thi bất kể ngoại lệ. Hi vọng điêu nay co ich.


2
Điều này sẽ hoạt động ngay cả khi bạn không sử dụng mệnh đề "cuối cùng" không trả lời câu hỏi vì OP muốn biết sự khác biệt, một ví dụ tốt sẽ gây ra lỗi khác với IOError, để cho thấy rằng khối mệnh đề cuối cùng được thực thi trước khi ngoại lệ được truyền đến người gọi.
Reda Drissi

2
Tôi không biết elselà một điều. Hữu ích để biết.
mazunki

8

Các khối mã không tương đương. Các finallykhoản cũng sẽ được chạy nếu run_code1()ném một ngoại lệ khác hơn TypeError, hoặc nếu run_code2()ném một ngoại lệ, trong khi other_code()ở phiên bản đầu tiên sẽ không được chạy trong những trường hợp này.


7

Trong ví dụ đầu tiên của bạn, điều gì xảy ra nếu run_code1()đưa ra một ngoại lệ không phải là TypeError? ... other_code()sẽ không được thực thi.

So sánh với finally:phiên bản: other_code()được đảm bảo thực hiện bất kể ngoại lệ nào được nêu ra.


7

Như đã giải thích trong tài liệu , finallymệnh đề này nhằm xác định các hành động dọn dẹp phải được thực hiện trong mọi trường hợp .

Nếu finallycó, nó chỉ định một trình xử lý 'dọn dẹp'. Các try khoản được thực thi, bao gồm bất kỳ exceptelsemệnh đề. Nếu một ngoại lệ xảy ra trong bất kỳ điều khoản nào và không được xử lý, ngoại lệ đó sẽ tạm thời được lưu. Các finallykhoản được thực thi. Nếu có một ngoại lệ được lưu, nó sẽ được nâng lên ở cuối finally mệnh đề.

Một ví dụ:

>>> 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'

Như bạn có thể thấy, finallymệnh đề được thực thi trong bất kỳ sự kiện nào. Việc TypeErrortăng bằng cách chia hai chuỗi không được xử lý bởi exceptmệnh đề và do đó được tăng lại sau khi finallymệnh đề đã được thực thi.

Trong các ứng dụng trong thế giới thực, mệnh đề cuối cùng rất hữu ích để giải phóng các tài nguyên bên ngoài (như các tệp hoặc kết nối mạng), bất kể việc sử dụng tài nguyên có thành công hay không.


4

Ví dụ hoàn hảo như sau:

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

finallylà để xác định "hành động dọn dẹp" . Các finallykhoản được thực hiện trong mọi trường hợp trước khi rời khỏi trytuyên bố, cho dù một ngoại lệ (ngay cả khi bạn không xử lý nó) đã xảy ra hay không.

Tôi thứ hai @ Byers là ví dụ.


2

Cuối cùng cũng có thể được sử dụng khi bạn muốn chạy mã "tùy chọn" trước khi chạy mã cho công việc chính của mình và mã tùy chọn đó có thể không thành công vì nhiều lý do.

Trong ví dụ sau, chúng tôi không biết chính xác loại ngoại lệ nào store_some_debug_infocó thể ném.

Chúng tôi có thể chạy:

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

Nhưng, hầu hết các linters sẽ phàn nàn về việc bắt quá mơ hồ về một ngoại lệ. Ngoài ra, vì chúng tôi chọn chỉ passvì lỗi, nên exceptkhối không thực sự tăng thêm giá trị.

try:
  store_some_debug_info()
finally:
  do_something_really_important()     

Đoạn mã trên có tác dụng tương tự như khối mã thứ 1 nhưng ngắn gọn hơn.


2

Sử dụng delphi một cách chuyên nghiệp trong một số năm đã dạy tôi bảo vệ thói quen dọn dẹp của mình bằng cách sử dụng cuối cùng. Delphi thực thi khá nhiều việc sử dụng cuối cùng để dọn sạch mọi tài nguyên được tạo trước khối thử, vì sợ bạn gây rò rỉ bộ nhớ. Đây cũng là cách Java, Python và Ruby hoạt động.

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

và tài nguyên sẽ được dọn sạch bất kể bạn làm gì giữa thử và cuối cùng. Ngoài ra, nó sẽ không được làm sạch nếu thực thi không bao giờ đạt đến trykhối. (tức là create_resourcechính nó ném một ngoại lệ) Nó làm cho mã của bạn "ngoại lệ an toàn".

Về lý do tại sao bạn thực sự cần một khối cuối cùng, không phải tất cả các ngôn ngữ làm. Trong C ++, nơi bạn đã tự động gọi các hàm hủy để thực thi dọn dẹp khi một ngoại lệ hủy bỏ ngăn xếp. Tôi nghĩ rằng đây là một bước tiến theo hướng mã sạch hơn so với thử ... cuối cùng là ngôn ngữ.

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

2

Một khối thử chỉ có một mệnh đề bắt buộc: Câu lệnh thử. Các ngoại lệ, các mệnh đề khác và cuối cùng là tùy chọn và dựa trên sở thích của người dùng.

cuối cùng: Trước khi Python rời khỏi câu lệnh try, nó sẽ chạy mã trong khối cuối cùng trong bất kỳ điều kiện nào, ngay cả khi nó kết thúc chương trình. Ví dụ: nếu Python gặp lỗi trong khi chạy mã trong khối ngoại trừ hoặc khối khác, khối cuối cùng sẽ vẫn được thực thi trước khi dừng chương trình.


1
Cái này sai. Tuyên bố ngoại trừ là bắt buộc. - Lucas Azevedo ngày 1 tháng 2 lúc 12:04 Điều này là sai, vì tôi vừa biên dịch và chạy chương trình Python 3.5 với khối thử cuối cùng, không có mệnh đề "ngoại trừ".
Rob Tow

2
Tôi đã thử điều này bản thân và với sự hoài nghi của tôi, điều khoản ngoại trừ là không bắt buộc.
Captainblack

1

Chạy các mã Python3 này để xem nhu cầu cuối cùng:

TRƯỜNG HỢP 1:

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))

TRƯỜNG HỢP2:

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))

Hãy thử các đầu vào sau mỗi lần:

  1. số nguyên ngẫu nhiên
  2. mã chính xác là 586 (Hãy thử mã này và bạn sẽ nhận được câu trả lời của mình)
  3. chuỗi ngẫu nhiên

** Ở giai đoạn đầu học Python.


1

Tôi đã cố chạy một mã mà tôi muốn đọc các bảng excel. Vấn đề là, nếu có một tập tin không có tên là: SheetSum tôi không thể di chuyển nó đến vị trí lỗi !! Mã tôi đã viết là:

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')

Lỗi đưa ra:

[WinError 32] Quá trình không thể truy cập tệp vì nó đang được sử dụng bởi một quy trình khác

Tôi đã phải thêm try except with finallykhối đầy đủ và nói rằng finallytôi cần phải đóng tệp trong mọi trường hợp như:

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')

Nếu không, tập tin vẫn còn mở là nền.

Nếu finallycó, nó chỉ định một trình xử lý dọn dẹp . Các try khoản được thực thi, bao gồm bất kỳ exceptelsemệnh đề. Nếu một ngoại lệ xảy ra trong bất kỳ điều khoản nào và không được xử lý, ngoại lệ đó sẽ tạm thời được lưu . Các finallykhoản được thực thi. Nếu có một ngoại lệ được lưu, nó sẽ được nâng lên ở cuối finally mệnh đề. Nếu finallymệnh đề đưa ra một ngoại lệ khác, ngoại lệ đã lưu được đặt làm bối cảnh của ngoại lệ mới.

..Như đây

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.