Python thử-khác


581

Mục đích sử dụng của elsemệnh đề tùy chọn của trycâu lệnh là gì?


1
Hầu hết các câu trả lời dường như tập trung vào lý do tại sao chúng ta không thể đặt tài liệu vào mệnh đề khác trong chính mệnh đề thử. Câu hỏi stackoverflow.com/questions/3996329 hỏi cụ thể tại sao mã mệnh đề khác không thể đi sau khối thử và câu hỏi đó bị lừa với câu hỏi này, nhưng tôi không thấy câu trả lời rõ ràng cho câu hỏi đó ở đây. Tôi cảm thấy stackoverflow.com/a/3996378/1503120 trả lời xuất sắc câu hỏi đó. Tôi cũng đã cố gắng làm sáng tỏ tầm quan trọng khác nhau của các mệnh đề khác nhau tại stackoverflow.com/a/22579805/1503120 .
jamadagni

Bạn muốn điều gì đó xảy ra nếu ngoại lệ không kích hoạt, trước khi dọn dẹp lần cuối, điều đó không bao giờ được cho là tự kích hoạt xử lý ngoại lệ tương tự.
benjimin

Câu trả lời:


858

Các câu lệnh trong elsekhối được thực thi nếu thực thi rơi xuống dưới cùng của try- nếu không có ngoại lệ. Thành thật mà nói, tôi chưa bao giờ tìm thấy một nhu cầu.

Tuy nhiên, Xử lý ngoại lệ ghi chú:

Việc sử dụng mệnh đề khác tốt hơn là thêm mã bổ sung vào mệnh đề try vì nó tránh vô tình bắt gặp một ngoại lệ không được nêu ra bởi mã được bảo vệ bởi lệnh thử ... ngoại trừ câu lệnh.

Vì vậy, nếu bạn có một phương pháp có thể, ví dụ, ném IOErrorvà bạn muốn bắt ngoại lệ, nhưng có một cách khác bạn muốn làm nếu thao tác đầu tiên thành công và bạn không muốn bắt IOError từ đó hoạt động đó, bạn có thể viết một cái gì đó như thế này:

try:
    operation_that_can_throw_ioerror()
except IOError:
    handle_the_exception_somehow()
else:
    # we don't want to catch the IOError if it's raised
    another_operation_that_can_throw_ioerror()
finally:
    something_we_always_need_to_do()

Nếu bạn đặt another_operation_that_can_throw_ioerror()sau operation_that_can_throw_ioerror, exceptcuộc gọi sẽ bắt lỗi thứ hai. Và nếu bạn đặt nó sau toàn bộ trykhối, nó sẽ luôn được chạy, và cho đến sau đó finally. Cho elsephép bạn chắc chắn

  1. Hoạt động thứ hai chỉ chạy nếu không có ngoại lệ,
  2. nó chạy trước finallykhối và
  3. bất kỳ IOErrornó tăng lên không bị bắt ở đây

7
Ngoài ra, hãy nhớ rằng các biến được sử dụng trong khối thử có thể được sử dụng trong khối khác, vì vậy bạn nên cân nhắc sử dụng biến thể này nếu bạn không mong đợi nhiều ngoại lệ hơn trong khối khác
WorldSEnder

3
Điều đó không quan trọng, bởi vì các biến trong phạm vi thử được nhìn thấy bên ngoài thử cho dù có khác hay không.
Reinderien

36
Không có thứ gọi là "biến phạm vi thử". Trong Python, phạm vi biến chỉ được thiết lập bởi các mô-đun, chức năng và hiểu, không phải cấu trúc điều khiển.
mhsmith

9
Mệnh đề khác cho phép bạn viết mã chỉ có ý nghĩa nếu một ngoại lệ không bị ném; mệnh đề ngoại trừ có thể đơn giản vượt qua. Nếu bạn đặt logic trong khối thử, bạn có nguy cơ âm thầm ẩn các lỗi trong mã của mình. Không bao giờ ngoại lệ squash bạn không mong đợi.
Alice Purcell

9
không rõ câu trả lời này có nghĩa là "rơi xuống đáy" nghĩa là gì - điều này không chỉ xảy ra vì một ngoại lệ mà còn vì một return, continuehoặc break.
Antti Haapala 3/03/2016

108

Có một lý do lớn để sử dụng else- phong cách và khả năng đọc. Nói chung, nên giữ mã có thể gây ra ngoại lệ gần mã liên quan đến chúng. Ví dụ, so sánh những điều này:

try:
    from EasyDialogs import AskPassword
    # 20 other lines
    getpass = AskPassword
except ImportError:
    getpass = default_getpass

try:
    from EasyDialogs import AskPassword
except ImportError:
    getpass = default_getpass
else:
    # 20 other lines
    getpass = AskPassword

Cách thứ hai là tốt khi exceptkhông thể quay lại sớm hoặc ném lại ngoại lệ. Nếu có thể, tôi đã có thể viết:

try:
    from EasyDialogs import AskPassword
except ImportError:
    getpass = default_getpass
    return False  # or throw Exception('something more descriptive')

# 20 other lines
getpass = AskPassword

Lưu ý: Trả lời được sao chép từ bản sao được đăng gần đây tại đây , do đó tất cả nội dung "AskPassword" này.


53

Một lần sử dụng: kiểm tra một số mã sẽ đưa ra một ngoại lệ.

try:
    this_should_raise_TypeError()
except TypeError:
    pass
except:
    assert False, "Raised the wrong exception type"
else:
    assert False, "Didn't raise any exception"

(Mã này nên được trừu tượng hóa thành một thử nghiệm chung hơn trong thực tế.)


50

Python thử-khác

Mục đích sử dụng của elsemệnh đề tùy chọn của câu lệnh try là gì?

Mục đích sử dụng là có một bối cảnh để chạy nhiều mã hơn nếu không có trường hợp ngoại lệ nào được dự kiến ​​sẽ được xử lý.

Bối cảnh này tránh vô tình xử lý các lỗi bạn không mong đợi.

Nhưng điều quan trọng để hiểu được điều kiện chính xác gây ra các khoản khác để chạy là, bởi vì return, continuebreakcó thể làm gián đoạn dòng điều khiển đến else.

Tóm tắt

Các elsetuyên bố chạy nếu có không có trường hợp ngoại lệ và nếu không bị gián đoạn bởi một return, continuehoặc breaktuyên bố.

Các câu trả lời khác bỏ lỡ phần cuối cùng.

Từ các tài liệu:

Các tùy chọn elseđiều khoản được thực hiện nếu và khi kiểm soát chảy ra khỏi cuối của trykhoản. *

(Thêm vào.) Và chú thích ghi:

* Hiện nay, kiểm soát “chảy ra khỏi cuối” ngoại trừ trong trường hợp của một ngoại lệ hoặc thực hiện một return, continuehoặc breaktuyên bố.

Nó không yêu cầu ít nhất một mệnh đề trước trừ mệnh đề ( xem ngữ pháp ). Vì vậy, nó thực sự không phải là "thử-khác", đó là "thử ngoại trừ khác (cuối cùng)," với else(và finally) là tùy chọn.

Các Bài chỉ dẫn Python nói rõ về việc sử dụng dự định:

Câu lệnh try ... ngoại trừ có một mệnh đề khác tùy chọn, mà khi có mặt, phải tuân theo tất cả các mệnh đề ngoại trừ mệnh đề. Nó rất hữu ích cho mã phải được thực thi nếu mệnh đề try không đưa ra một ngoại lệ. Ví dụ:

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

Việc sử dụng mệnh đề khác tốt hơn là thêm mã bổ sung vào mệnh đề try vì nó tránh vô tình bắt một ngoại lệ không được nêu ra bởi mã được bảo vệ bởi lệnh thử ... ngoại trừ câu lệnh.

Ví dụ phân biệt elseso với mã theo trykhối

Nếu bạn xử lý một lỗi, elsekhối sẽ không chạy. Ví dụ:

def handle_error():
    try:
        raise RuntimeError('oops!')
    except RuntimeError as error:
        print('handled a RuntimeError, no big deal.')
    else:
        print('if this prints, we had no error!') # won't print!
    print('And now we have left the try block!')  # will print!

Và bây giờ,

>>> handle_error()
handled a RuntimeError, no big deal.
And now we have left the try block!

26

Thử ngoại trừ khác là tuyệt vời để kết hợp mô hình EAFP với gõ vịt :

try:
  cs = x.cleanupSet
except AttributeError:
  pass
else:
  for v in cs:
    v.cleanup()

Bạn có thể thấy mã ngây thơ này là tốt:

try:
  for v in x.cleanupSet:
    v.clenaup()
except AttributeError:
  pass

Đây là một cách tuyệt vời để vô tình che giấu các lỗi nghiêm trọng trong mã của bạn. Tôi đánh máy dọn dẹp ở đó, nhưng AttributionError sẽ cho tôi biết đang bị nuốt. Tồi tệ hơn, điều gì sẽ xảy ra nếu tôi viết nó một cách chính xác, nhưng phương thức dọn dẹp đôi khi được thông qua một loại người dùng có thuộc tính sai, khiến nó âm thầm thất bại giữa chừng và để lại một tệp không được tiết lộ? Chúc may mắn gỡ lỗi đó.


19

Tôi thấy nó thực sự hữu ích khi bạn đã dọn dẹp để làm điều đó phải được thực hiện ngay cả khi có một ngoại lệ:

try:
    data = something_that_can_go_wrong()
except Exception as e: # yes, I know that's a bad way to do it...
    handle_exception(e)
else:
    do_stuff(data)
finally:
    clean_up()

9

Mặc dù bạn không thể nghĩ đến việc sử dụng nó ngay bây giờ, bạn có thể đặt cược rằng phải có một cách sử dụng nó. Đây là một mẫu không tưởng tượng:

Với else:

a = [1,2,3]
try:
    something = a[2]
except:
    print "out of bounds"
else:
    print something

Không có else:

try:
    something = a[2]
except:
    print "out of bounds"

if "something" in locals():
    print something

Ở đây bạn có biến somethingđược xác định nếu không có lỗi được ném. Bạn có thể loại bỏ cái này bên ngoài trykhối, nhưng sau đó nó yêu cầu một số phát hiện lộn xộn nếu một biến được xác định.


3
Có gì sai với something = a[2]; print somethingbên trong thử: khối?
S.Lott

@ S.Lott không có gì, nhưng nếu ai đó gửi cho bạn một danh sách và bạn không muốn hiển thị dữ liệu nếu nó không đủ dài vì có thể nó bị hỏng thì sao?
Không biết

12
S. Lott: 'in một cái gì đó' có thể đưa ra một ngoại lệ khác mà bạn không muốn chặn.
Darius Bacon

Tôi không thấy sự khác biệt. Nếu tôi nhận được một ngoại lệ trong giới hạn, nó sẽ in "ra khỏi giới hạn". Hiểu rồi Nếu tôi nhận được một số ngoại lệ khác, nó sẽ bị chặn bởi khối mã này. Nếu tôi không có ngoại lệ, hành vi là in giá trị của một cái gì đó, đó là [2]. Tôi không thấy những gì người khác làm trong ví dụ này.
S.Lott

3
Giá trị của 'cái gì đó', khi được in, có thể gây ra lỗi trong phương thức __str __ () của nó. Trong khi giá trị đó thực sự chỉ là 2 trong ví dụ này, bạn cũng có thể chỉ ra rằng không có ngoại lệ ngoài giới hạn ở đây.
Darius Bacon

8

Có một ví dụ tốt đẹp của try-elsetrong PEP 380 . Về cơ bản, nó đi xuống để thực hiện xử lý ngoại lệ khác nhau trong các phần khác nhau của thuật toán.

Nó giống như thế này:

try:
    do_init_stuff()
except:
    handle_init_suff_execption()
else:
    try:
        do_middle_stuff()
    except:
        handle_middle_stuff_exception()

Điều này cho phép bạn viết mã xử lý ngoại lệ gần nơi xảy ra ngoại lệ.


7

Từ lỗi và ngoại lệ # Xử lý ngoại lệ - docs.python.org

Câu try ... exceptlệnh có một elsemệnh đề tùy chọn , mà khi có mặt, phải tuân theo tất cả các mệnh đề ngoại trừ mệnh đề. Nó rất hữu ích cho mã phải được thực thi nếu mệnh đề try không đưa ra một ngoại lệ. Ví dụ:

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

Việc sử dụng mệnh đề khác tốt hơn là thêm mã bổ sung vào mệnh đề try vì nó tránh vô tình bắt một ngoại lệ không được nêu ra bởi mã được bảo vệ bởi lệnh thử ... ngoại trừ câu lệnh.


6

Nhìn vào tài liệu tham khảo Python có vẻ như elseđược thực thi sau trykhi không có ngoại lệ. Mệnh đề khác tùy chọn được thực thi nếu và khi điều khiển chảy ra khỏi cuối mệnh đề try. 2 Các ngoại lệ trong mệnh đề khác không được xử lý bởi các mệnh đề trước trừ các mệnh đề.

Đi sâu vào python có một ví dụ trong đó, nếu tôi hiểu chính xác, trong trykhối họ cố gắng nhập một mô-đun, khi đó bạn không có ngoại lệ và liên kết mặc định nhưng khi nó hoạt động, bạn có một tùy chọn để đi vào elsekhối và liên kết những gì được yêu cầu (xem liên kết cho ví dụ và giải thích).

Nếu bạn cố gắng thực hiện công việc trong catchkhối, nó có thể đưa ra một ngoại lệ khác - tôi đoán đó là nơi mà elsekhối này có ích.


4
"Các ngoại lệ trong mệnh đề khác không được xử lý bởi các mệnh đề trước trừ các mệnh đề." Đó là phần hữu ích. Cảm ơn bạn.
geowa4

"Mệnh đề khác tùy chọn được thực thi nếu và khi điều khiển chảy ra khỏi cuối mệnh đề thử" là một sự khác biệt khác, vì bạn có thể quay trở lại khỏi trykhối.
Tomer W

4

Đó là nó. Khối 'khác' của mệnh đề ngoại trừ thử tồn tại cho mã chạy khi (và chỉ khi) hoạt động đã thử thành công. Nó có thể được sử dụng, và nó có thể bị lạm dụng.

try:
    fp= open("configuration_file", "rb")
except EnvironmentError:
    confdata= '' # it's ok if the file can't be opened
else:
    confdata= fp.read()
    fp.close()

# your code continues here
# working with (possibly empty) confdata

Cá nhân, tôi thích nó và sử dụng nó khi thích hợp. Nó ngữ nghĩa nhóm các tuyên bố.


2

Có lẽ một cách sử dụng có thể là:

#debug = []

def debuglog(text, obj=None):
    " Simple little logger. "
    try:
        debug   # does global exist?
    except NameError:
        pass    # if not, don't even bother displaying
    except:
        print('Unknown cause. Debug debuglog().')
    else:
        # debug does exist.
        # Now test if you want to log this debug message
        # from caller "obj"
        try:
            if obj in debug:
                print(text)     # stdout
        except TypeError:
            print('The global "debug" flag should be an iterable.')
        except:
            print('Unknown cause. Debug debuglog().')

def myfunc():
    debuglog('Made it to myfunc()', myfunc)

debug = [myfunc,]
myfunc()

Có lẽ điều này sẽ dẫn bạn quá sử dụng.


2

Tôi đã tìm thấy try: ... else:cấu trúc hữu ích trong trường hợp bạn đang chạy các truy vấn cơ sở dữ liệu và ghi lại kết quả của các truy vấn đó vào một cơ sở dữ liệu riêng biệt có cùng loại / hương vị. Giả sử tôi có rất nhiều luồng công nhân tất cả các xử lý truy vấn cơ sở dữ liệu được gửi đến hàng đợi

#in a long running loop
try:
    query = queue.get()
    conn = connect_to_db(<main db>)
    curs = conn.cursor()
    try:
        curs.execute("<some query on user input that may fail even if sanitized">)
    except DBError:
        logconn = connect_to_db(<logging db>)
        logcurs = logconn.cursor()
        logcurs.execute("<update in DB log with record of failed query")
        logcurs.close()
        logconn.close()
    else:

        #we can't put this in main try block because an error connecting
        #to the logging DB would be indistinguishable from an error in 
        #the mainquery 

        #We can't put this after the whole try: except: finally: block
        #because then we don't know if the query was successful or not

        logconn = connect_to_db(<logging db>)
        logcurs = logconn.cursor()
        logcurs.execute("<update in DB log with record of successful query")
        logcurs.close()
        logconn.close()
        #do something in response to successful query
except DBError:
    #This DBError is because of a problem with the logging database, but 
    #we can't let that crash the whole thread over what might be a
    #temporary network glitch
finally:
    curs.close()
    conn.close()
    #other cleanup if necessary like telling the queue the task is finished

Tất nhiên, nếu bạn có thể phân biệt giữa các ngoại lệ có thể bị ném, bạn không phải sử dụng điều này, nhưng nếu mã phản ứng với một đoạn mã thành công có thể ném ngoại lệ giống như đoạn thành công và bạn không thể chỉ hãy để ngoại lệ thứ hai có thể xảy ra, hoặc quay trở lại ngay lập tức khi thành công (sẽ giết chết chuỗi trong trường hợp của tôi), sau đó điều này sẽ có ích.


1

Một elsekhối thường có thể tồn tại để bổ sung cho chức năng xảy ra trong mọi exceptkhối.

try:
    test_consistency(valuable_data)
except Except1:
    inconsistency_type = 1
except Except2:
    inconsistency_type = 2
except:
    # Something else is wrong
    raise
else:
    inconsistency_type = 0

"""
Process each individual inconsistency down here instead of
inside the except blocks. Use 0 to mean no inconsistency.
"""

Trong trường hợp này, inconsistency_typeđược đặt trong mỗi khối ngoại trừ, để hành vi được bổ sung trong trường hợp không có lỗi trong else.

Tất nhiên, tôi mô tả điều này như một mô hình có thể xuất hiện trong mã của riêng bạn một ngày nào đó. Trong trường hợp cụ thể này, bạn chỉ cần đặt inconsistency_typethành 0 trước trykhối.


1

Đây là một nơi khác mà tôi thích sử dụng mẫu này:

 while data in items:
     try
        data = json.loads(data)
     except ValueError as e:
        log error
     else:
        # work on the `data`

1
Bạn chỉ có thể sử dụng continuethay thế - mô hình "thoát ra sớm". Điều này cho phép bạn bỏ mệnh đề "other" và thụt lề của nó, làm cho mã dễ đọc hơn.
Malthe

1

Một trong những tình huống sử dụng tôi có thể nghĩ đến là các ngoại lệ không thể đoán trước, có thể tránh được nếu bạn thử lại. Chẳng hạn, khi các hoạt động trong khối thử bao gồm các số ngẫu nhiên:

while True:
    try:
        r = random.random()
        some_operation_that_fails_for_specific_r(r)
    except Exception:
        continue
    else:
        break

Nhưng nếu ngoại lệ có thể được dự đoán, bạn nên luôn luôn chọn xác nhận trước một ngoại lệ. Tuy nhiên, không phải mọi thứ đều có thể dự đoán được, vì vậy mẫu mã này có vị trí của nó.


1
Bạn có thể làm điều này để đặt phần breakbên trong tryở cuối, IMO sạch hơn và bạn không cần else. Ngoài ra, continuekhông thực sự cần thiết, bạn có thể chỉ pass.
Dirbaio

1

Tôi đã tìm thấy elsehữu ích để xử lý một tệp cấu hình có thể không chính xác:

try:
    value, unit = cfg['lock'].split()
except ValueError:
    msg = 'lock monitoring config must consist of two words separated by white space'
    self.log('warn', msg)
else:
     # get on with lock monitoring if config is ok

Một ngoại lệ đọc lockcấu hình sẽ vô hiệu hóa giám sát khóa và ValueErrors ghi lại một thông báo cảnh báo hữu ích.


1

Giả sử logic lập trình của bạn phụ thuộc vào việc từ điển có mục nhập với khóa đã cho hay không. Bạn có thể kiểm tra kết quả củadict.get(key) việc sử dụng if... else...cấu trúc hoặc bạn có thể làm:

try:
    val = dic[key]
except KeyError:
    do_some_stuff()
else:
    do_some_stuff_with_val(val)

-1

Tôi sẽ thêm một trường hợp sử dụng khác có vẻ đơn giản khi xử lý các phiên DB:

    # getting a DB connection 
    conn = db.engine.connect()

    # and binding to a DB session
    session = db.get_session(bind=conn)

    try:
        # we build the query to DB
        q = session.query(MyTable).filter(MyTable.col1 == 'query_val')

        # i.e retrieve one row
        data_set = q.one_or_none()

        # return results
        return [{'col1': data_set.col1, 'col2': data_set.col2, ...}]

    except:
        # here we make sure to rollback the transaction, 
        # handy when we update stuff into DB
        session.rollback()
        raise

    else:
        # when no errors then we can commit DB changes
        session.commit()

    finally:
        # and finally we can close the session
        session.close()

-17

Các else:khối là khó hiểu và (gần như) vô dụng. Nó cũng là một phần của forwhile tuyên bố.

Trên thực tế, ngay cả trên một tầng lớp if,else: có thể bị lạm dụng theo những cách thực sự khủng khiếp tạo ra những lỗi rất khó tìm.

Xem xét điều này.

   if a < 10:
       # condition stated explicitly
   elif a > 10 and b < 10:
       # condition confusing but at least explicit
   else:
       # Exactly what is true here?
       # Can be hard to reason out what condition is true

Hãy suy nghĩ kỹ về else:. Nó thường là một vấn đề. Tránh nó ngoại trừ trong một if-statement và thậm chí sau đó xem xét ghi lại elseđiều kiện - để làm cho nó rõ ràng.


6
Tôi sẽ không đồng ý với điều này. Trong khối "if-elif", "other" được sử dụng làm "mặc định" sẽ được sử dụng trong khối "trường hợp" của ngôn ngữ C. Luôn luôn nên xử lý trường hợp "mặc định" ngay cả khi bạn nghĩ rằng bạn đã bao gồm tất cả các trường hợp trong các điều kiện khác nhau.
Josip

1
@Josip: được sử dụng làm "mặc định" có thể gây nhầm lẫn. Vấn đề là xác định rõ điều kiện "mặc định" này. Một điều kiện mặc định được xác định kém có thể là nguyên nhân gốc của hành vi lỗi. Khác có thể là một nguyên nhân của sự nhầm lẫn. Nó nên được suy nghĩ rất cẩn thận trong mọi trường hợp, không chỉ cố gắng, trong và trong khi, mà nếu như là tốt.
S.Lott

5
Chà, đoạn mã trên hoàn toàn trừu tượng và không làm gì có ý nghĩa, vì vậy - không có gì lạ khi nó khó hiểu.
julx

1
@ S.Lott "Nó sẽ làm giảm sự hối hận" - và quan điểm của tôi là điều này là sai. Tôi nghĩ rằng chúng ta chỉ có một sự khác biệt thực sự trong ý kiến. Lập trình viên xấu luôn tìm cách viết chương trình lỗi. Luôn luôn. Các lập trình viên giỏi luôn tìm kiếm các thực tiễn tốt và có thể viết mã tốt chỉ bằng bất kỳ ngôn ngữ nào. Loại bỏ các cấu trúc hữu ích chỉ cung cấp ít năng lượng hơn cho các lập trình viên giỏi trong khi không giúp đỡ những người xấu vì họ có khả năng phát minh ra vô số cách để cải thiện mọi thứ.
julx

5
Xem xét: if x > 0: return "yes"if x <= 0: return "no". Bây giờ một người đến và thay đổi một trong những điều kiện để nói x > 1nhưng quên thay đổi một điều kiện khác. Làm thế nào là giảm số lượng lỗi sẽ được cam kết. if elsemệnh đề đôi khi cách nhau nhiều dòng. DRY là một thực hành tốt, thường xuyên hơn không, thực sự. (Xin lỗi cho đăng bài gấp đôi).
julx
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.