Python try-else


578

Для чого передбачається використання необов'язкового elseпункту tryтвердження?


1
Більшість відповідей, як видається, зосереджені на тому, чому ми не можемо просто поставити матеріал у інший пункт у самій спробу. Питання stackoverflow.com/questions/3996329 конкретно задає питання, чому інший код пункту не може перейти після самого блоку спробу, і це питання дублюється на цей, але я не бачу чіткої відповіді на це питання тут. Я вважаю, що stackoverflow.com/a/3996378/1503120 чудово відповідає на це питання. Я також намагався з'ясувати різну значимість різних статей на сайті stackoverflow.com/a/22579805/1503120 .
jamadagni

Ви хочете, щоб щось сталося, якщо виняток не спрацьовує, перед остаточним очищенням, це ніколи не повинно ініціювати таку ж обробку винятків.
benjimin

Відповіді:


857

Виписки в elseблоці виконуються, якщо виконання випадає з нижньої частини поля try- якщо не було винятком. Чесно кажучи, я ніколи не знаходив потреби.

Однак обробка виключень зазначає:

Використовувати інший пункт краще, ніж додавати додатковий код до пункту спробу, тому що це дозволяє уникнути випадкового лову винятку, який не був піднятий захищеним кодом спробу ... крім оператора.

Отже, якщо у вас є метод, який може, наприклад, кинути an IOError, і ви хочете вилучити винятки, які він збільшує, але ви хочете зробити щось інше, якщо перша операція буде успішною, і ви не хочете зловити IOError від цю операцію, ви можете написати щось подібне:

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

Якщо ви просто поставите another_operation_that_can_throw_ioerror()після цього operation_that_can_throw_ioerror, exceptвикличе помилки другого дзвінка. І якщо ви поставите його після цілого tryблоку, він завжди буде запущений, і не після цього finally. elseДозволяє переконатися ,

  1. друга операція виконується лише якщо немає винятку,
  2. запускається перед finallyблоком, і
  3. будь- IOErrorякі вирощування тут не спіймані

7
Також майте на увазі, що змінні, які використовуються в пробному блоці CAN, можуть використовуватися в блоці else, тому вам слід завжди розглянути можливість використання цього варіанту, якщо ви не очікуєте більше винятків у блоці else
WorldSEnder

3
Це не має значення, тому що спробовані змінні бачуться поза спробою, чи є інша чи ні.
Reinderien

36
Немає такого поняття, як "змінна перевірка". У Python змінні області застосування встановлюються лише модулями, функціями та розуміннями, а не структурами управління.
mhsmith

9
Інший пункт дозволяє писати код, який має сенс, лише якщо виняток не був кинутий; за винятком пункту може просто пройти. Якщо ви вкладете логіку в блок спробу, ви ризикуєте мовчки приховати помилки у своєму коді. Ніколи не стискайте винятків, яких ви не очікували.
Аліса Перселл

9
з цієї відповіді незрозуміло, що означає "падіння з дна" - це відбувається не тільки через виняток, але й через a return, continueабо break.
Антті Хаапала

108

Є одна велика причина використання else- стиль та читабельність. Як правило, добре зберігати код, який може спричинити винятки біля коду, який ними займається. Наприклад, порівняйте:

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

Другий з них хороший, коли exceptне може повернутися рано чи повторно викинути виняток. Якщо можливо, я б написав:

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

# 20 other lines
getpass = AskPassword

Примітка. Відповідь скопійована з нещодавно опублікованого тут дубліката , звідси і все це "AskPassword".


53

Одне використання: перевірити деякий код, який повинен створити виняток.

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

(Цей код повинен бути абстрагований на більш загальному тесті на практиці.)


50

Python try-else

Для чого передбачається використання необов'язкового elseзастереження спробувати test?

Передбачуване використання має мати контекст для запуску більшої кількості коду, якщо не було винятків, коли це очікувалося для обробки.

Цей контекст дозволяє уникнути випадкових помилок, яких ви не очікували.

Але важливо зрозуміти точні умови , які викликають ще пункт про перспективу, тому що return, continueі breakможе перервати потік управління в else.

Підсумки

elseЗаява працює , якщо немає жодного винятку , і якщо не переривається return, continueабо breakзаяву.

Інші відповіді пропускають цю останню частину.

З документів:

Додатковий elseпункт виконується , якщо і коли управління відтікає в кінці цього tryпункту. *

(Сміливий додав.) А виноска йде:

* В даний час, управління «відтікає кінця» , за винятком того, в разі виключення або виконання return, continueабо breakзаяви.

Для цього потрібен принаймні один попередній, крім пункту ( див. Граматику ). Тож насправді це не "пробувати інше", це "пробувати, окрім іншого ((остаточно)", при цьому elsefinally) є необов'язковим.

В Python Tutorial конкретизує передбачуваного використання:

Оператор 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()

Використовувати інший пункт краще, ніж додавати додатковий код до пункту спробу, тому що це дозволяє уникнути випадкового лову винятку, який не був піднятий захищеним кодом спробу ... крім оператора.

Приклад диференціювання elseвід коду за tryблоком

Якщо ви обробляєте помилку, elseблок не запускається. Наприклад:

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!

І зараз,

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

26

Спробуйте, крім іншого, відмінно підходить для поєднання схеми EAFP з набором тексту :

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

Ви можете сказати, що цей наївний код добре:

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

Це прекрасний спосіб випадкового приховування серйозних помилок у вашому коді. Я надрукував там очищення, але AttributeError, який дав би мені знати, проковтується. Гірше, що, якби я написав це правильно, але метод очищення періодично передавали тип користувача, який мав неправильний атрибут, внаслідок чого він мовчки провалювався на півдорозі та залишав файл незакритим? Удачі налагодження цього.


19

Я вважаю це дійсно корисним, коли у вас є очищення, що потрібно зробити, навіть якщо є виняток:

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

Незважаючи на те, що ви зараз не можете подумати про його використання, ви можете зробити ставку, що це має бути корисно. Ось непередуманий зразок:

З else:

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

Без else:

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

if "something" in locals():
    print something

Тут ви somethingвизначили змінну, якщо не буде видано жодної помилки. Ви можете видалити це за межами tryблоку, але тоді воно потребує деякого безладного виявлення, якщо визначена змінна.


3
Що не так із something = a[2]; print somethingблоком try: block?
С.Лотт

@ S.Lott нічого, але що робити, якщо хтось надсилає вам список, і ви не хочете відображати дані, якщо їх недостатньо довго, тому що вони, ймовірно, пошкоджені?
Невідомо

12
С. Лотт: "надрукувати щось" може призвести до іншого винятку, який ви не хочете перехоплювати.
Дарій Бекон

Я не бачу різниці. Якщо у мене виняток виходить за межі, він друкується "поза межами". Зрозумів. Якщо я отримаю якийсь інший виняток, цей блок коду не сприймається. Якщо я не став винятком, поведінка полягає в тому, щоб надрукувати значення чогось, що є [2]. Я не бачу, що ще робить у цьому прикладі.
С.Лотт

3
При друкуванні значення "щось" може призвести до помилки в його __str __ () методі. Хоча це значення насправді лише 2 у цьому прикладі, ви можете так само зауважити, що і тут немає винятку поза межами.
Дарій Бекон

8

Там хороший приклад try-elseв PEP 380 . В основному, це зводиться до виконання різної обробки виключень у різних частинах алгоритму.

Це щось подібне:

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

Це дозволяє записати код оброблення виключень ближче до місця, де відбувається виняток.


7

З помилок та винятків # Обробка виключень - docs.python.org

У try ... exceptзаяві є необов’язкове elseзастереження, яке, за наявності, повинно відповідати всім, окрім пунктів Це корисно для коду, який повинен бути виконаний, якщо пробний пункт не викликає винятку. Наприклад:

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

Використовувати інший пункт краще, ніж додавати додатковий код до пункту спробу, тому що це дозволяє уникнути випадкового лову винятку, який не був піднятий захищеним кодом спробу ... крім оператора.


6

Переглядаючи посилання Python, здається, що elseвоно виконується після того, tryяк немає винятку. Необов'язковий інший пункт виконується, якщо і коли управління відтікає від кінця спробу. 2 Винятки з іншого пункту не обробляються попередніми, окрім пунктів.

Занурення в python має приклад, коли, якщо я правильно розумію, у tryблоці вони намагаються імпортувати модуль, коли це не вдається, ви отримуєте виняток і прив'язуєте за замовчуванням, але коли він працює, у вас є можливість перейти до elseблоку та зв’язати те, що потрібно (див. посилання для прикладу та пояснення).

Якщо ви спробували виконати роботу в catchблоці, це може спричинити ще один виняток - я думаю, саме тут цей elseблок стане у нагоді.


4
"Винятки з іншого пункту не обробляються попередніми, окрім пунктів." Це корисна частина. Дякую.
geowa4

"Необов'язковий інший пункт виконується, якщо і коли контроль відходить від кінця пункту спробу", це ще одна відмінність, оскільки ви можете повернутися з tryблоку.
Tomer W

4

Це воно. Блок 'else' пункту спробу виключення існує для коду, який запускається, коли (і лише тоді), коли випробувана операція успішна. Це можна використовувати, і ним можна зловживати.

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

Особисто мені це подобається і використовую, коли це доречно. Він семантично групує твердження.


2

Можливо, користь може бути:

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

Можливо, це теж призведе до вашої користі.


2

Я вважаю цю try: ... else:конструкцію корисною в ситуації, коли ви виконуєте запити до бази даних та записуєте результати цих запитів в окрему базу даних того ж аромату / типу. Скажімо, у мене є безліч робочих ниток, всі запити обробки баз даних, подані до черги

#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

Звичайно, якщо ви можете розрізняти можливі винятки, які можуть бути викинуті, вам не доведеться використовувати це, але якщо код, що реагує на вдалий фрагмент коду, може викинути той самий виняток, що і успішний фрагмент, і ви не можете просто відпустіть другий можливий виняток або повертайтеся негайно до успіху (що могло б вбити нитку в моєму випадку), тоді це стане в нагоді.


1

elseБлок часто може існувати на додаток до функціональності , яка відбувається в кожному exceptблоці.

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

У цьому випадку inconsistency_typeвстановлюється у кожному, крім блоку, так що поведінка доповнюється у випадку без помилок у else.

Звичайно, я описую це як зразок, який колись може з’явитися у вашому власному коді. У цьому конкретному випадку ви просто все-таки встановите inconsistency_type0 перед tryблоком.


1

Ось ще одне місце, де я люблю використовувати цю схему:

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

1
Ви можете просто використовувати continueзамість цього - шаблон "вирватися рано". Це дозволяє скинути пункт "else" та його відступ, що полегшує читання коду.
мальте

1

Один із сценаріїв використання, який я можу придумати, - це непередбачувані винятки, які можна обійти, якщо спробувати ще раз. Наприклад, коли операції в блоці спробу включають випадкові числа:

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

Але якщо виняток можна передбачити, завжди слід вибирати перевірку заздалегідь над винятком. Однак не все можна передбачити, тому цей шаблон коду має своє місце.


1
Це можна зробити, поставивши breakвнутрішню tryсторону в кінці, яка чистіша IMO, і вона вам не потрібна else. Крім того, continueце не дуже потрібно, ви можете просто pass.
Дірбайо

1

Я вважаю elseкорисним для роботи з можливо неправильним файлом конфігурації:

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

Виняток, що читає lockконфігурацію, відключає моніторинг блокування, а ValueErrors записує корисне попередження.


1

Припустимо, ваша логіка програмування залежить від того, чи має словник запис із заданим ключем. Ви можете перевірити результат, dict.get(key)використовуючи if... else...конструкт, або ви можете зробити:

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

-1

Я б додав ще один випадок використання, який здається прямо перед обробкою сеансів БД:

    # 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

else:Блок збиває з пантелику , і (майже) марно. Це також частина forта whileтверджень.

Насправді, навіть за ifтвердженням, зловмисники else:можуть зловживати по-справжньому жахливими способами, створюючи помилки, які дуже важко знайти.

Розглянемо це.

   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

Подумайте двічі else:. Це взагалі проблема. Уникайте цього, окрім ifзаяви, і навіть тоді розглядайте питання документування elseумови - щоб зробити це явним.


6
Я б не погодився з цим. У блоці "if-elif" використовується "else", оскільки "за замовчуванням" буде використано в блоці "case" мови С. Завжди рекомендується обробляти справу "за замовчуванням", навіть якщо ви думаєте, що ви охоплювали всі справи в різних умовах.
Йосип

1
@Josip: використовується як "за замовчуванням" може бути заплутаним. Питання полягає в чіткому визначенні умови, яка є цим "замовчуванням". Неправильно визначена умова за замовчуванням може бути першопричиною помилок поведінки. Інше може стати причиною плутанини. Це слід продумати дуже ретельно у всіх випадках, а не просто намагатися на час і на час, але якщо також.
С.Лотт

5
Ну, наведений вище код є абсолютно абстрактним і не робить нічого змістовного, так що - недарма це заплутано.
julx

1
@ S.Lott "Це зменшило бідність" - і я заперечую, що це помилково. Я думаю, у нас просто справжня різниця в думках. Погані програмісти завжди знаходять способи написання баггі-програм. Завжди. Хороші програмісти завжди прагнуть передового досвіду і можуть написати хороший код майже будь-якою мовою. Усунення корисних конструкцій просто дає меншу силу хорошим програмістам, а не особливо допомагає поганим, оскільки вони здатні винайти нескінченну кількість способів вдосконалити речі.
julx

5
Поміркуйте: if x > 0: return "yes"і if x <= 0: return "no". Зараз людина приходить і змінює одну з умов сказати, x > 1але забуває змінити іншу. Як це зменшити кількість помилок, які будуть вчинені. if elseЗастереження іноді багато рядків один від одного. DRY - це хороша практика, набагато частіше, ніж ні, насправді. (вибачте за подвійний пост).
julx
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.