The library presented in this article is becoming obsolete and un-maintained. I recommend you to read this post about tenacity instead.
I don’t often write about tools I use when for my daily software development tasks. I recently realized that I really should start to share more often my workflows and weapons of choice.
One thing that I have a hard time enduring while doing Python code reviews, is people writing utility code that is not directly tied to the core of their business. This looks to me as wasted time maintaining code that should be reused from elsewhere.
So today I’d like to start with retrying, a Python package that you can use to… retry anything.
It’s OK to fail
Often in computing, you have to deal with external resources. That means accessing resources you don’t control. Resources that can fail, become flapping, unreachable or unavailable.
Most applications don’t deal with that at all, and explode in flight, leaving a skeptical user in front of the computer. A lot of software engineers refuse to deal with failure, and don’t bother handling this kind of scenario in their code.
In the best case, applications usually handle simply the case where the external reached system is out of order. They log something, and inform the user that it should try again later.
In this cloud computing area, we tend to design software components with service-oriented architecture in mind. That means having a lot of different services talking to each others over the network. And we all know that networks tend to fail, and distributed systems too. Writing software with failing being part of normal operation is a terrific idea.
Retrying
In order to help applications with the handling of these potential failures, you need a plan. Leaving to the user the burden to «try again later» is rarely a good choice. Therefore, most of the time you want your application to retry.
Retrying an action is a full strategy on its own, with a lot of options. You can retry only on certain condition, and with the number of tries based on time (e.g. every second), based on a number of tentative (e.g. retry 3 times and abort), based on the problem encountered, or even on all of those.
For all of that, I use the retrying library that you can retrieve easily on PyPI.
retrying provides a decorator called retry
that you can use on top of any function or method in Python to make it retry in case of failure. By default, retry
calls your function endlessly until it returns rather than raising an error.
import random
from retrying import retry
@retry
def pick_one():
if random.randint(0, 10) != 1:
raise Exception("1 was not picked")
This will execute the function pick_one
until 1
is returned by random.randint
.
retry
accepts a few arguments, such as the minimum and maximum delays to use, which also can be randomized. Randomizing delay is a good strategy to avoid detectable pattern or congestion. But more over, it supports exponential delay, which can be used to implement exponential backoff, a good solution for retrying tasks while really avoiding congestion. It’s especially handy for background tasks.
@retry(wait_exponential_multiplier=1000, wait_exponential_max=10000)
def wait_exponential_1000():
print "Wait 2^x * 1000 milliseconds between each retry, up to 10 seconds, then 10 seconds afterwards"
raise Exception("Retry!")
You can mix that with a maximum delay, which can give you a good strategy to retry for a while, and then fail anyway:
# Stop retrying after 30 seconds anyway
>>> @retry(wait_exponential_multiplier=1000, wait_exponential_max=10000, stop_max_delay=30000)
... def wait_exponential_1000():
... print "Wait 2^x * 1000 milliseconds between each retry, up to 10 seconds, then 10 seconds afterwards"
... raise Exception("Retry!")
...
>>> wait_exponential_1000()
Wait 2^x * 1000 milliseconds between each retry, up to 10 seconds, then 10 seconds afterwards
Wait 2^x * 1000 milliseconds between each retry, up to 10 seconds, then 10 seconds afterwards
Wait 2^x * 1000 milliseconds between each retry, up to 10 seconds, then 10 seconds afterwards
Wait 2^x * 1000 milliseconds between each retry, up to 10 seconds, then 10 seconds afterwards
Wait 2^x * 1000 milliseconds between each retry, up to 10 seconds, then 10 seconds afterwards
Wait 2^x * 1000 milliseconds between each retry, up to 10 seconds, then 10 seconds afterwards
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python2.7/site-packages/retrying.py", line 49, in wrapped_f
return Retrying(*dargs, **dkw).call(f, *args, **kw)
File "/usr/local/lib/python2.7/site-packages/retrying.py", line 212, in call
raise attempt.get()
File "/usr/local/lib/python2.7/site-packages/retrying.py", line 247, in get
six.reraise(self.value[0], self.value[1], self.value[2])
File "/usr/local/lib/python2.7/site-packages/retrying.py", line 200, in call
attempt = Attempt(fn(*args, **kwargs), attempt_number, False)
File "<stdin>", line 4, in wait_exponential_1000
Exception: Retry!
A pattern I use very often, is the ability to retry only based on some exception type. You can specify a function to filter out exception you want to ignore or the one you want to use to retry.
def retry_on_ioerror(exc):
return isinstance(exc, IOError)
@retry(retry_on_exception=retry_on_ioerror)
def read_file():
with open("myfile", "r") as f:
return f.read()
retry
will call the function passed as retry_on_exception
with the exception raised as first argument. It’s up to the function to then return a boolean indicating if a retry should be performed or not. In the example above, this will only retry to read the file if an IOError
occurs; if any other exception type is raised, no retry will be performed.
The same pattern can be implemented using the keyword argument retry_on_result
, where you can provide a function that analyses the result and retry based on it.
def retry_if_file_empty(result):
return len(result) <= 0
@retry(retry_on_result=retry_if_file_empty)
def read_file():
with open("myfile", "r") as f:
return f.read()
This example will read the file until it stops being empty. If the file does not exist, an IOError
is raised, and the default behavior which triggers retry on all exceptions kicks-in – the retry is therefore performed.
That’s it! retry
is really a good and small library that you should leverage rather than implementing your own half-baked solution!
Tenacity
Tenacity is an Apache 2.0 licensed general-purpose retrying library, written in
Python, to simplify the task of adding retry behavior to just about anything.
It originates from a fork of retrying which is sadly no longer
maintained. Tenacity isn’t
api compatible with retrying but adds significant new functionality and
fixes a number of longstanding bugs.
The simplest use case is retrying a flaky function whenever an Exception
occurs until a value is returned.
.. testcode:: import random from tenacity import retry @retry def do_something_unreliable(): if random.randint(0, 10) > 1: raise IOError("Broken sauce, everything is hosed!!!111one") else: return "Awesome sauce!" print(do_something_unreliable())
.. testoutput:: :hide: Awesome sauce!
.. toctree:: :hidden: :maxdepth: 2 changelog api
Features
- Generic Decorator API
- Specify stop condition (i.e. limit by number of attempts)
- Specify wait condition (i.e. exponential backoff sleeping between attempts)
- Customize retrying on Exceptions
- Customize retrying on expected returned result
- Retry on coroutines
- Retry code block with context manager
Installation
To install tenacity, simply:
Examples
Basic Retry
.. testsetup:: * import logging # # Note the following import is used for demonstration convenience only. # Production code should always explicitly import the names it needs. # from tenacity import * class MyException(Exception): pass
As you saw above, the default behavior is to retry forever without waiting when
an exception is raised.
.. testcode:: @retry def never_gonna_give_you_up(): print("Retry forever ignoring Exceptions, don't wait between retries") raise Exception
Stopping
Let’s be a little less persistent and set some boundaries, such as the number
of attempts before giving up.
.. testcode:: @retry(stop=stop_after_attempt(7)) def stop_after_7_attempts(): print("Stopping after 7 attempts") raise Exception
We don’t have all day, so let’s set a boundary for how long we should be
retrying stuff.
.. testcode:: @retry(stop=stop_after_delay(10)) def stop_after_10_s(): print("Stopping after 10 seconds") raise Exception
You can combine several stop conditions by using the | operator:
.. testcode:: @retry(stop=(stop_after_delay(10) | stop_after_attempt(5))) def stop_after_10_s_or_5_retries(): print("Stopping after 10 seconds or 5 retries") raise Exception
Waiting before retrying
Most things don’t like to be polled as fast as possible, so let’s just wait 2
seconds between retries.
.. testcode:: @retry(wait=wait_fixed(2)) def wait_2_s(): print("Wait 2 second between retries") raise Exception
Some things perform best with a bit of randomness injected.
.. testcode:: @retry(wait=wait_random(min=1, max=2)) def wait_random_1_to_2_s(): print("Randomly wait 1 to 2 seconds between retries") raise Exception
Then again, it’s hard to beat exponential backoff when retrying distributed
services and other remote endpoints.
.. testcode:: @retry(wait=wait_exponential(multiplier=1, min=4, max=10)) def wait_exponential_1(): print("Wait 2^x * 1 second between each retry starting with 4 seconds, then up to 10 seconds, then 10 seconds afterwards") raise Exception
Then again, it’s also hard to beat combining fixed waits and jitter (to
help avoid thundering herds) when retrying distributed services and other
remote endpoints.
.. testcode:: @retry(wait=wait_fixed(3) + wait_random(0, 2)) def wait_fixed_jitter(): print("Wait at least 3 seconds, and add up to 2 seconds of random delay") raise Exception
When multiple processes are in contention for a shared resource, exponentially
increasing jitter helps minimise collisions.
.. testcode:: @retry(wait=wait_random_exponential(multiplier=1, max=60)) def wait_exponential_jitter(): print("Randomly wait up to 2^x * 1 seconds between each retry until the range reaches 60 seconds, then randomly up to 60 seconds afterwards") raise Exception
Sometimes it’s necessary to build a chain of backoffs.
.. testcode:: @retry(wait=wait_chain(*[wait_fixed(3) for i in range(3)] + [wait_fixed(7) for i in range(2)] + [wait_fixed(9)])) def wait_fixed_chained(): print("Wait 3s for 3 attempts, 7s for the next 2 attempts and 9s for all attempts thereafter") raise Exception
Whether to retry
We have a few options for dealing with retries that raise specific or general
exceptions, as in the cases here.
.. testcode:: class ClientError(Exception): """Some type of client error.""" @retry(retry=retry_if_exception_type(IOError)) def might_io_error(): print("Retry forever with no wait if an IOError occurs, raise any other errors") raise Exception @retry(retry=retry_if_not_exception_type(ClientError)) def might_client_error(): print("Retry forever with no wait if any error other than ClientError occurs. Immediately raise ClientError.") raise Exception
We can also use the result of the function to alter the behavior of retrying.
.. testcode:: def is_none_p(value): """Return True if value is None""" return value is None @retry(retry=retry_if_result(is_none_p)) def might_return_none(): print("Retry with no wait if return value is None")
See also these methods:
.. testcode:: retry_if_exception retry_if_exception_type retry_if_not_exception_type retry_unless_exception_type retry_if_result retry_if_not_result retry_if_exception_message retry_if_not_exception_message retry_any retry_all
We can also combine several conditions:
.. testcode:: def is_none_p(value): """Return True if value is None""" return value is None @retry(retry=(retry_if_result(is_none_p) | retry_if_exception_type())) def might_return_none(): print("Retry forever ignoring Exceptions with no wait if return value is None")
Any combination of stop, wait, etc. is also supported to give you the freedom
to mix and match.
It’s also possible to retry explicitly at any time by raising the TryAgain
exception:
.. testcode:: @retry def do_something(): result = something_else() if result == 23: raise TryAgain
Error Handling
Normally when your function fails its final time (and will not be retried again based on your settings),
a RetryError is raised. The exception your code encountered will be shown somewhere in the middle
of the stack trace.
If you would rather see the exception your code encountered at the end of the stack trace (where it
is most visible), you can set reraise=True.
.. testcode:: @retry(reraise=True, stop=stop_after_attempt(3)) def raise_my_exception(): raise MyException("Fail") try: raise_my_exception() except MyException: # timed out retrying pass
Before and After Retry, and Logging
It’s possible to execute an action before any attempt of calling the function
by using the before callback function:
.. testcode:: import logging import sys logging.basicConfig(stream=sys.stderr, level=logging.DEBUG) logger = logging.getLogger(__name__) @retry(stop=stop_after_attempt(3), before=before_log(logger, logging.DEBUG)) def raise_my_exception(): raise MyException("Fail")
In the same spirit, It’s possible to execute after a call that failed:
.. testcode:: import logging import sys logging.basicConfig(stream=sys.stderr, level=logging.DEBUG) logger = logging.getLogger(__name__) @retry(stop=stop_after_attempt(3), after=after_log(logger, logging.DEBUG)) def raise_my_exception(): raise MyException("Fail")
It’s also possible to only log failures that are going to be retried. Normally
retries happen after a wait interval, so the keyword argument is called
before_sleep
:
.. testcode:: import logging import sys logging.basicConfig(stream=sys.stderr, level=logging.DEBUG) logger = logging.getLogger(__name__) @retry(stop=stop_after_attempt(3), before_sleep=before_sleep_log(logger, logging.DEBUG)) def raise_my_exception(): raise MyException("Fail")
Statistics
You can access the statistics about the retry made over a function by using the
retry attribute attached to the function and its statistics attribute:
.. testcode:: @retry(stop=stop_after_attempt(3)) def raise_my_exception(): raise MyException("Fail") try: raise_my_exception() except Exception: pass print(raise_my_exception.retry.statistics)
.. testoutput:: :hide: ...
Custom Callbacks
You can also define your own callbacks. The callback should accept one
parameter called retry_state
that contains all information about current
retry invocation.
For example, you can call a custom callback function after all retries failed,
without raising an exception (or you can re-raise or do anything really)
.. testcode:: def return_last_value(retry_state): """return the result of the last call attempt""" return retry_state.outcome.result() def is_false(value): """Return True if value is False""" return value is False # will return False after trying 3 times to get a different result @retry(stop=stop_after_attempt(3), retry_error_callback=return_last_value, retry=retry_if_result(is_false)) def eventually_return_false(): return False
RetryCallState
retry_state
argument is an object of RetryCallState class:
.. autoclass:: tenacity.RetryCallState Constant attributes: .. autoattribute:: start_time(float) :annotation: .. autoattribute:: retry_object(BaseRetrying) :annotation: .. autoattribute:: fn(callable) :annotation: .. autoattribute:: args(tuple) :annotation: .. autoattribute:: kwargs(dict) :annotation: Variable attributes: .. autoattribute:: attempt_number(int) :annotation: .. autoattribute:: outcome(tenacity.Future or None) :annotation: .. autoattribute:: outcome_timestamp(float or None) :annotation: .. autoattribute:: idle_for(float) :annotation: .. autoattribute:: next_action(tenacity.RetryAction or None) :annotation:
Other Custom Callbacks
It’s also possible to define custom callbacks for other keyword arguments.
.. function:: my_stop(retry_state) :param RetryState retry_state: info about current retry invocation :return: whether or not retrying should stop :rtype: bool
.. function:: my_wait(retry_state) :param RetryState retry_state: info about current retry invocation :return: number of seconds to wait before next retry :rtype: float
.. function:: my_retry(retry_state) :param RetryState retry_state: info about current retry invocation :return: whether or not retrying should continue :rtype: bool
.. function:: my_before(retry_state) :param RetryState retry_state: info about current retry invocation
.. function:: my_after(retry_state) :param RetryState retry_state: info about current retry invocation
.. function:: my_before_sleep(retry_state) :param RetryState retry_state: info about current retry invocation
Here’s an example with a custom before_sleep
function:
.. testcode:: import logging logging.basicConfig(stream=sys.stderr, level=logging.DEBUG) logger = logging.getLogger(__name__) def my_before_sleep(retry_state): if retry_state.attempt_number < 1: loglevel = logging.INFO else: loglevel = logging.WARNING logger.log( loglevel, 'Retrying %s: attempt %s ended with: %s', retry_state.fn, retry_state.attempt_number, retry_state.outcome) @retry(stop=stop_after_attempt(3), before_sleep=my_before_sleep) def raise_my_exception(): raise MyException("Fail") try: raise_my_exception() except RetryError: pass
Changing Arguments at Run Time
You can change the arguments of a retry decorator as needed when calling it by
using the retry_with function attached to the wrapped function:
.. testcode:: @retry(stop=stop_after_attempt(3)) def raise_my_exception(): raise MyException("Fail") try: raise_my_exception.retry_with(stop=stop_after_attempt(4))() except Exception: pass print(raise_my_exception.retry.statistics)
.. testoutput:: :hide: ...
If you want to use variables to set up the retry parameters, you don’t have
to use the retry decorator — you can instead use Retrying directly:
.. testcode:: def never_good_enough(arg1): raise Exception('Invalid argument: {}'.format(arg1)) def try_never_good_enough(max_attempts=3): retryer = Retrying(stop=stop_after_attempt(max_attempts), reraise=True) retryer(never_good_enough, 'I really do try')
Retrying code block
Tenacity allows you to retry a code block without the need to wraps it in an
isolated function. This makes it easy to isolate failing block while sharing
context. The trick is to combine a for loop and a context manager.
.. testcode:: from tenacity import Retrying, RetryError, stop_after_attempt try: for attempt in Retrying(stop=stop_after_attempt(3)): with attempt: raise Exception('My code is failing!') except RetryError: pass
You can configure every details of retry policy by configuring the Retrying
object.
With async code you can use AsyncRetrying.
.. testcode:: from tenacity import AsyncRetrying, RetryError, stop_after_attempt async def function(): try: async for attempt in AsyncRetrying(stop=stop_after_attempt(3)): with attempt: raise Exception('My code is failing!') except RetryError: pass
In both cases, you may want to set the result to the attempt so it’s available
in retry strategies like retry_if_result
. This can be done accessing the
retry_state
property:
.. testcode:: from tenacity import AsyncRetrying, retry_if_result async def function(): async for attempt in AsyncRetrying(retry=retry_if_result(lambda x: x < 3)): with attempt: result = 1 # Some complex calculation, function call, etc. if not attempt.retry_state.outcome.failed: attempt.retry_state.set_result(result) return result
Async and retry
Finally, retry
works also on asyncio and Tornado (>= 4.5) coroutines.
Sleeps are done asynchronously too.
@retry async def my_async_function(loop): await loop.getaddrinfo('8.8.8.8', 53)
@retry @tornado.gen.coroutine def my_async_function(http_client, url): yield http_client.fetch(url)
You can even use alternative event loops such as curio or Trio by passing the correct sleep function:
@retry(sleep=trio.sleep) async def my_async_function(loop): await asks.get('https://example.org')
Contribute
- Check for open issues or open a fresh issue to start a discussion around a
feature idea or a bug. - Fork the repository on GitHub to start making your changes to the
master branch (or branch off of it). - Write a test which shows that the bug was fixed or that the feature works as
expected. - Add a changelog
- Make the docs better (or more detailed, or more easier to read, or …)
Changelogs
reno is used for managing changelogs. Take a look at their usage docs.
The doc generation will automatically compile the changelogs. You just need to add them.
# Opens a template file in an editor
tox -e reno -- new some-slug-for-my-change --edit
Содержание:развернуть
- Как устроен механизм исключений
- Как обрабатывать исключения в Python (try except)
-
As — сохраняет ошибку в переменную
-
Finally — выполняется всегда
-
Else — выполняется когда исключение не было вызвано
-
Несколько блоков except
-
Несколько типов исключений в одном блоке except
-
Raise — самостоятельный вызов исключений
-
Как пропустить ошибку
- Исключения в lambda функциях
- 20 типов встроенных исключений в Python
- Как создать свой тип Exception
Программа, написанная на языке Python, останавливается сразу как обнаружит ошибку. Ошибки могут быть (как минимум) двух типов:
- Синтаксические ошибки — возникают, когда написанное выражение не соответствует правилам языка (например, написана лишняя скобка);
- Исключения — возникают во время выполнения программы (например, при делении на ноль).
Синтаксические ошибки исправить просто (если вы используете IDE, он их подсветит). А вот с исключениями всё немного сложнее — не всегда при написании программы можно сказать возникнет или нет в данном месте исключение. Чтобы приложение продолжило работу при возникновении проблем, такие ошибки нужно перехватывать и обрабатывать с помощью блока try/except
.
Как устроен механизм исключений
В Python есть встроенные исключения, которые появляются после того как приложение находит ошибку. В этом случае текущий процесс временно приостанавливается и передает ошибку на уровень вверх до тех пор, пока она не будет обработано. Если ошибка не будет обработана, программа прекратит свою работу (а в консоли мы увидим Traceback с подробным описанием ошибки).
💁♂️ Пример: напишем скрипт, в котором функция ожидает число, а мы передаём сроку (это вызовет исключение «TypeError»):
def b(value):
print("-> b")
print(value + 1) # ошибка тут
def a(value):
print("-> a")
b(value)
a("10")
> -> a
> -> b
> Traceback (most recent call last):
> File "test.py", line 11, in <module>
> a("10")
> File "test.py", line 8, in a
> b(value)
> File "test.py", line 3, in b
> print(value + 1)
> TypeError: can only concatenate str (not "int") to str
В данном примере мы запускаем файл «test.py» (через консоль). Вызывается функция «a«, внутри которой вызывается функция «b«. Все работает хорошо до сточки print(value + 1)
. Тут интерпретатор понимает, что нельзя конкатенировать строку с числом, останавливает выполнение программы и вызывает исключение «TypeError».
Далее ошибка передается по цепочке в обратном направлении: «b» → «a» → «test.py«. Так как в данном примере мы не позаботились обработать эту ошибку, вся информация по ошибке отобразится в консоли в виде Traceback.
Traceback (трассировка) — это отчёт, содержащий вызовы функций, выполненные в определенный момент. Трассировка помогает узнать, что пошло не так и в каком месте это произошло.
Traceback лучше читать снизу вверх ↑
В нашем примере Traceback
содержится следующую информацию (читаем снизу вверх):
TypeError
— тип ошибки (означает, что операция не может быть выполнена с переменной этого типа);can only concatenate str (not "int") to str
— подробное описание ошибки (конкатенировать можно только строку со строкой);- Стек вызова функций (1-я линия — место, 2-я линия — код). В нашем примере видно, что в файле «test.py» на 11-й линии был вызов функции «a» со строковым аргументом «10». Далее был вызов функции «b».
print(value + 1)
это последнее, что было выполнено — тут и произошла ошибка. most recent call last
— означает, что самый последний вызов будет отображаться последним в стеке (в нашем примере последним выполнилсяprint(value + 1)
).
В Python ошибку можно перехватить, обработать, и продолжить выполнение программы — для этого используется конструкция try ... except ...
.
Как обрабатывать исключения в Python (try except)
В Python исключения обрабатываются с помощью блоков try/except
. Для этого операция, которая может вызвать исключение, помещается внутрь блока try
. А код, который должен быть выполнен при возникновении ошибки, находится внутри except
.
Например, вот как можно обработать ошибку деления на ноль:
try:
a = 7 / 0
except:
print('Ошибка! Деление на 0')
Здесь в блоке try
находится код a = 7 / 0
— при попытке его выполнить возникнет исключение и выполнится код в блоке except
(то есть будет выведено сообщение «Ошибка! Деление на 0»). После этого программа продолжит свое выполнение.
💭 PEP 8 рекомендует, по возможности, указывать конкретный тип исключения после ключевого слова except
(чтобы перехватывать и обрабатывать конкретные исключения):
try:
a = 7 / 0
except ZeroDivisionError:
print('Ошибка! Деление на 0')
Однако если вы хотите перехватывать все исключения, которые сигнализируют об ошибках программы, используйте тип исключения Exception
:
try:
a = 7 / 0
except Exception:
print('Любая ошибка!')
As — сохраняет ошибку в переменную
Перехваченная ошибка представляет собой объект класса, унаследованного от «BaseException». С помощью ключевого слова as
можно записать этот объект в переменную, чтобы обратиться к нему внутри блока except
:
try:
file = open('ok123.txt', 'r')
except FileNotFoundError as e:
print(e)
> [Errno 2] No such file or directory: 'ok123.txt'
В примере выше мы обращаемся к объекту класса «FileNotFoundError» (при выводе на экран через print
отобразится строка с полным описанием ошибки).
У каждого объекта есть поля, к которым можно обращаться (например если нужно логировать ошибку в собственном формате):
import datetime
now = datetime.datetime.now().strftime("%d-%m-%Y %H:%M:%S")
try:
file = open('ok123.txt', 'r')
except FileNotFoundError as e:
print(f"{now} [FileNotFoundError]: {e.strerror}, filename: {e.filename}")
> 20-11-2021 18:42:01 [FileNotFoundError]: No such file or directory, filename: ok123.txt
Finally — выполняется всегда
При обработке исключений можно после блока try
использовать блок finally
. Он похож на блок except
, но команды, написанные внутри него, выполняются обязательно. Если в блоке try
не возникнет исключения, то блок finally
выполнится так же, как и при наличии ошибки, и программа возобновит свою работу.
Обычно try/except
используется для перехвата исключений и восстановления нормальной работы приложения, а try/finally
для того, чтобы гарантировать выполнение определенных действий (например, для закрытия внешних ресурсов, таких как ранее открытые файлы).
В следующем примере откроем файл и обратимся к несуществующей строке:
file = open('ok.txt', 'r')
try:
lines = file.readlines()
print(lines[5])
finally:
file.close()
if file.closed:
print("файл закрыт!")
> файл закрыт!
> Traceback (most recent call last):
> File "test.py", line 5, in <module>
> print(lines[5])
> IndexError: list index out of range
Даже после исключения «IndexError», сработал код в секции finally
, который закрыл файл.
p.s. данный пример создан для демонстрации, в реальном проекте для работы с файлами лучше использовать менеджер контекста with.
Также можно использовать одновременно три блока try/except/finally
. В этом случае:
- в
try
— код, который может вызвать исключения; - в
except
— код, который должен выполниться при возникновении исключения; - в
finally
— код, который должен выполниться в любом случае.
def sum(a, b):
res = 0
try:
res = a + b
except TypeError:
res = int(a) + int(b)
finally:
print(f"a = {a}, b = {b}, res = {res}")
sum(1, "2")
> a = 1, b = 2, res = 3
Else — выполняется когда исключение не было вызвано
Иногда нужно выполнить определенные действия, когда код внутри блока try
не вызвал исключения. Для этого используется блок else
.
Допустим нужно вывести результат деления двух чисел и обработать исключения в случае попытки деления на ноль:
b = int(input('b = '))
c = int(input('c = '))
try:
a = b / c
except ZeroDivisionError:
print('Ошибка! Деление на 0')
else:
print(f"a = {a}")
> b = 10
> c = 1
> a = 10.0
В этом случае, если пользователь присвоит переменной «с» ноль, то появится исключение и будет выведено сообщение «‘Ошибка! Деление на 0′», а код внутри блока else
выполняться не будет. Если ошибки не будет, то на экране появятся результаты деления.
Несколько блоков except
В программе может возникнуть несколько исключений, например:
- Ошибка преобразования введенных значений к типу
float
(«ValueError»); - Деление на ноль («ZeroDivisionError»).
В Python, чтобы по-разному обрабатывать разные типы ошибок, создают несколько блоков except
:
try:
b = float(input('b = '))
c = float(input('c = '))
a = b / c
except ZeroDivisionError:
print('Ошибка! Деление на 0')
except ValueError:
print('Число введено неверно')
else:
print(f"a = {a}")
> b = 10
> c = 0
> Ошибка! Деление на 0
> b = 10
> c = питон
> Число введено неверно
Теперь для разных типов ошибок есть свой обработчик.
Несколько типов исключений в одном блоке except
Можно также обрабатывать в одном блоке except сразу несколько исключений. Для этого они записываются в круглых скобках, через запятую сразу после ключевого слова except
. Чтобы обработать сообщения «ZeroDivisionError» и «ValueError» в одном блоке записываем их следующим образом:
try:
b = float(input('b = '))
c = float(input('c = '))
a = b / c
except (ZeroDivisionError, ValueError) as er:
print(er)
else:
print('a = ', a)
При этом переменной er
присваивается объект того исключения, которое было вызвано. В результате на экран выводятся сведения о конкретной ошибке.
Raise — самостоятельный вызов исключений
Исключения можно генерировать самостоятельно — для этого нужно запустить оператор raise
.
min = 100
if min > 10:
raise Exception('min must be less than 10')
> Traceback (most recent call last):
> File "test.py", line 3, in <module>
> raise Exception('min value must be less than 10')
> Exception: min must be less than 10
Перехватываются такие сообщения точно так же, как и остальные:
min = 100
try:
if min > 10:
raise Exception('min must be less than 10')
except Exception:
print('Моя ошибка')
> Моя ошибка
Кроме того, ошибку можно обработать в блоке except
и пробросить дальше (вверх по стеку) с помощью raise
:
min = 100
try:
if min > 10:
raise Exception('min must be less than 10')
except Exception:
print('Моя ошибка')
raise
> Моя ошибка
> Traceback (most recent call last):
> File "test.py", line 5, in <module>
> raise Exception('min must be less than 10')
> Exception: min must be less than 10
Как пропустить ошибку
Иногда ошибку обрабатывать не нужно. В этом случае ее можно пропустить с помощью pass
:
try:
a = 7 / 0
except ZeroDivisionError:
pass
Исключения в lambda функциях
Обрабатывать исключения внутри lambda функций нельзя (так как lambda записывается в виде одного выражения). В этом случае нужно использовать именованную функцию.
20 типов встроенных исключений в Python
Иерархия классов для встроенных исключений в Python выглядит так:
BaseException
SystemExit
KeyboardInterrupt
GeneratorExit
Exception
ArithmeticError
AssertionError
...
...
...
ValueError
Warning
Все исключения в Python наследуются от базового BaseException
:
SystemExit
— системное исключение, вызываемое функциейsys.exit()
во время выхода из приложения;KeyboardInterrupt
— возникает при завершении программы пользователем (чаще всего при нажатии клавиш Ctrl+C);GeneratorExit
— вызывается методомclose
объектаgenerator
;Exception
— исключения, которые можно и нужно обрабатывать (предыдущие были системными и их трогать не рекомендуется).
От Exception
наследуются:
1 StopIteration
— вызывается функцией next в том случае если в итераторе закончились элементы;
2 ArithmeticError
— ошибки, возникающие при вычислении, бывают следующие типы:
FloatingPointError
— ошибки при выполнении вычислений с плавающей точкой (встречаются редко);OverflowError
— результат вычислений большой для текущего представления (не появляется при операциях с целыми числами, но может появиться в некоторых других случаях);ZeroDivisionError
— возникает при попытке деления на ноль.
3 AssertionError
— выражение, используемое в функции assert
неверно;
4 AttributeError
— у объекта отсутствует нужный атрибут;
5 BufferError
— операция, для выполнения которой требуется буфер, не выполнена;
6 EOFError
— ошибка чтения из файла;
7 ImportError
— ошибка импортирования модуля;
8 LookupError
— неверный индекс, делится на два типа:
IndexError
— индекс выходит за пределы диапазона элементов;KeyError
— индекс отсутствует (для словарей, множеств и подобных объектов);
9 MemoryError
— память переполнена;
10 NameError
— отсутствует переменная с данным именем;
11 OSError
— исключения, генерируемые операционной системой:
ChildProcessError
— ошибки, связанные с выполнением дочернего процесса;ConnectionError
— исключения связанные с подключениями (BrokenPipeError, ConnectionResetError, ConnectionRefusedError, ConnectionAbortedError);FileExistsError
— возникает при попытке создания уже существующего файла или директории;FileNotFoundError
— генерируется при попытке обращения к несуществующему файлу;InterruptedError
— возникает в том случае если системный вызов был прерван внешним сигналом;IsADirectoryError
— программа обращается к файлу, а это директория;NotADirectoryError
— приложение обращается к директории, а это файл;PermissionError
— прав доступа недостаточно для выполнения операции;ProcessLookupError
— процесс, к которому обращается приложение не запущен или отсутствует;TimeoutError
— время ожидания истекло;
12 ReferenceError
— попытка доступа к объекту с помощью слабой ссылки, когда объект не существует;
13 RuntimeError
— генерируется в случае, когда исключение не может быть классифицировано или не подпадает под любую другую категорию;
14 NotImplementedError
— абстрактные методы класса нуждаются в переопределении;
15 SyntaxError
— ошибка синтаксиса;
16 SystemError
— сигнализирует о внутренне ошибке;
17 TypeError
— операция не может быть выполнена с переменной этого типа;
18 ValueError
— возникает когда в функцию передается объект правильного типа, но имеющий некорректное значение;
19 UnicodeError
— исключение связанное с кодирование текста в unicode
, бывает трех видов:
UnicodeEncodeError
— ошибка кодирования;UnicodeDecodeError
— ошибка декодирования;UnicodeTranslateError
— ошибка переводаunicode
.
20 Warning
— предупреждение, некритическая ошибка.
💭 Посмотреть всю цепочку наследования конкретного типа исключения можно с помощью модуля inspect
:
import inspect
print(inspect.getmro(TimeoutError))
> (<class 'TimeoutError'>, <class 'OSError'>, <class 'Exception'>, <class 'BaseException'>, <class 'object'>)
📄 Подробное описание всех классов встроенных исключений в Python смотрите в официальной документации.
Как создать свой тип Exception
В Python можно создавать свои исключения. При этом есть одно обязательное условие: они должны быть потомками класса Exception
:
class MyError(Exception):
def __init__(self, text):
self.txt = text
try:
raise MyError('Моя ошибка')
except MyError as er:
print(er)
> Моя ошибка
С помощью try/except
контролируются и обрабатываются ошибки в приложении. Это особенно актуально для критически важных частей программы, где любые «падения» недопустимы (или могут привести к негативным последствиям). Например, если программа работает как «демон», падение приведет к полной остановке её работы. Или, например, при временном сбое соединения с базой данных, программа также прервёт своё выполнение (хотя можно было отловить ошибку и попробовать соединиться в БД заново).
Вместе с try/except
можно использовать дополнительные блоки. Если использовать все блоки описанные в статье, то код будет выглядеть так:
try:
# попробуем что-то сделать
except (ZeroDivisionError, ValueError) as e:
# обрабатываем исключения типа ZeroDivisionError или ValueError
except Exception as e:
# исключение не ZeroDivisionError и не ValueError
# поэтому обрабатываем исключение общего типа (унаследованное от Exception)
# сюда не сходят исключения типа GeneratorExit, KeyboardInterrupt, SystemExit
else:
# этот блок выполняется, если нет исключений
# если в этом блоке сделать return, он не будет вызван, пока не выполнился блок finally
finally:
# этот блок выполняется всегда, даже если нет исключений else будет проигнорирован
# если в этом блоке сделать return, то return в блоке
Подробнее о работе с исключениями в Python можно ознакомиться в официальной документации.
The try except statement can handle exceptions. Exceptions may happen when you run a program.
Exceptions are errors that happen during execution of the program. Python won’t tell you about errors like syntax errors (grammar faults), instead it will abruptly stop.
An abrupt exit is bad for both the end user and developer.
Instead of an emergency halt, you can use a try except statement to properly deal with the problem. An emergency halt will happen if you do not properly handle exceptions.
Related course: Complete Python Programming Course & Exercises
What are exceptions in Python?
Python has built-in exceptions which can output an error. If an error occurs while running the program, it’s called an exception.
If an exception occurs, the type of exception is shown. Exceptions needs to be dealt with or the program will crash. To handle exceptions, the try-catch
block is used.
Some exceptions you may have seen before are FileNotFoundError
, ZeroDivisionError
or ImportError
but there are many more.
All exceptions in Python inherit from the class BaseException. If you open the Python interactive shell and type the following statement it will list all built-in exceptions:
The idea of the try-except clause is to handle exceptions (errors at runtime). The syntax of the try-except block is:
1 |
try: |
The idea of the try-except block is this:
-
try: the code with the exception(s) to catch. If an exception is raised, it jumps straight into the except block.
-
except: this code is only executed if an exception occured in the try block. The except block is required with a try block, even if it contains only the pass statement.
It may be combined with the else and finally keywords.
-
else: Code in the else block is only executed if no exceptions were raised in the try block.
-
finally: The code in the finally block is always executed, regardless of if a an exception was raised or not.
Catching Exceptions in Python
The try-except block can handle exceptions. This prevents abrupt exits of the program on error. In the example below we purposely raise an exception.
1 |
try: |
After the except block, the program continues. Without a try-except block, the last line wouldn’t be reached as the program would crash.
$ python3 example.pyDivided by zero
Should reach here
In the above example we catch the specific exception ZeroDivisionError. You can handle any exception like this:
1 |
try: |
You can write different logic for each type of exception that happens:
1 |
try: |
Related course: Complete Python Programming Course & Exercises
try-except
Lets take do a real world example of the try-except block.
The program asks for numeric user input. Instead the user types characters in the input box. The program normally would crash. But with a try-except block it can be handled properly.
The try except statement prevents the program from crashing and properly deals with it.
1 |
try: |
Entering invalid input, makes the program continue normally:
The try except statement can be extended with the finally keyword, this will be executed if no exception is thrown:
1 |
finally: |
The program continues execution if no exception has been thrown.
There are different kinds of exceptions: ZeroDivisionError, NameError, TypeError and so on. Sometimes modules define their own exceptions.
The try-except block works for function calls too:
1 |
def fail(): |
This outputs:
$ python3 example.pyException occured
Program continues
If you are a beginner, then I highly recommend this book.
try finally
A try-except block can have the finally clause (optionally). The finally clause is always executed.
So the general idea is:
1 |
try: |
For instance: if you open a file you’ll want to close it, you can do so in the finally clause.
1 |
try: |
try else
The else clause is executed if and only if no exception is raised. This is different from the finally clause that’s always executed.
1 |
try: |
Output:
No exception occured
We always do this
You can catch many types of exceptions this way, where the else clause is executed only if no exception happens.
1 |
try: |
Raise Exception
Exceptions are raised when an error occurs. But in Python you can also force an exception to occur with the keyword raise
.
Any type of exception can be raised:
1 |
>>> raise MemoryError("Out of memory") |
1 |
>>> raise ValueError("Wrong value") |
Related course: Complete Python Programming Course & Exercises
Built-in exceptions
A list of Python’s Built-in Exceptions is shown below. This list shows the Exception and why it is thrown (raised).
Exception | Cause of Error |
---|---|
AssertionError | if assert statement fails. |
AttributeError | if attribute assignment or reference fails. |
EOFError | if the input() functions hits end-of-file condition. |
FloatingPointError | if a floating point operation fails. |
GeneratorExit | Raise if a generator’s close() method is called. |
ImportError | if the imported module is not found. |
IndexError | if index of a sequence is out of range. |
KeyError | if a key is not found in a dictionary. |
KeyboardInterrupt | if the user hits interrupt key (Ctrl+c or delete). |
MemoryError | if an operation runs out of memory. |
NameError | if a variable is not found in local or global scope. |
NotImplementedError | by abstract methods. |
OSError | if system operation causes system related error. |
OverflowError | if result of an arithmetic operation is too large to be represented. |
ReferenceError | if a weak reference proxy is used to access a garbage collected referent. |
RuntimeError | if an error does not fall under any other category. |
StopIteration | by next() function to indicate that there is no further item to be returned by iterator. |
SyntaxError | by parser if syntax error is encountered. |
IndentationError | if there is incorrect indentation. |
TabError | if indentation consists of inconsistent tabs and spaces. |
SystemError | if interpreter detects internal error. |
SystemExit | by sys.exit() function. |
TypeError | if a function or operation is applied to an object of incorrect type. |
UnboundLocalError | if a reference is made to a local variable in a function or method, but no value has been bound to that variable. |
UnicodeError | if a Unicode-related encoding or decoding error occurs. |
UnicodeEncodeError | if a Unicode-related error occurs during encoding. |
UnicodeDecodeError | if a Unicode-related error occurs during decoding. |
UnicodeTranslateError | if a Unicode-related error occurs during translating. |
ValueError | if a function gets argument of correct type but improper value. |
ZeroDivisionError | if second operand of division or modulo operation is zero. |
User-defined Exceptions
Python has many standard types of exceptions, but they may not always serve your purpose.
Your program can have your own type of exceptions.
To create a user-defined exception, you have to create a class that inherits from Exception.
1 |
class LunchError(Exception): |
You made a user-defined exception named LunchError in the above code. You can raise this new exception if an error occurs.
Outputs your custom error:
$ python3 example.py
Traceback (most recent call last):
File “example.py”, line 5, in
raise LunchError(“Programmer went to lunch”)
main.LunchError: Programmer went to lunch
Your program can have many user-defined exceptions. The program below throws exceptions based on a new projects money:
1 |
class NoMoneyException(Exception): |
Here are some sample runs:
$ python3 example.py
Enter a balance: 500
Traceback (most recent call last):
File “example.py”, line 10, in
raise NoMoneyException
main.NoMoneyException
$ python3 example.py
$ python3 example.py
Enter a balance: 100000
Traceback (most recent call last):
File “example.py”, line 12, in
raise OutOfBudget
main.OutOfBudget
It is a good practice to put all user-defined exceptions in a separate file (exceptions.py or errors.py). This is common practice in standard modules too.
If you are a beginner, then I highly recommend this book.
Exercises
- Can try-except be used to catch invalid keyboard input?
- Can try-except catch the error if a file can’t be opened?
- When would you not use try-except?
Download examples
В этом руководстве мы расскажем, как обрабатывать исключения в Python с помощью try
и except
. Рассмотрим общий синтаксис и простые примеры, обсудим, что может пойти не так, и предложим меры по исправлению положения.
Зачастую разработчик может предугадать возникновение ошибок при работе даже синтаксически и логически правильной программы. Эти ошибки могут быть вызваны неверными входными данными или некоторыми предсказуемыми несоответствиями.
Для обработки большей части этих ошибок как исключений в Python есть блоки try
и except
.
Для начала разберем синтаксис операторов try и except в Python. Общий шаблон представлен ниже:
try: # В этом блоке могут быть ошибки except <error type>: # Сделай это для обработки исключения; # выполняется, если блок try выбрасывает ошибку else: # Сделай это, если блок try выполняется успешно, без ошибок finally: # Этот блок выполняется всегда
Давайте посмотрим, для чего используются разные блоки.
Блок try
Блок try
— это блок кода, который вы хотите попробовать выполнить. Однако во время выполнения из-за какого-нибудь исключения могут возникнуть ошибки. Поэтому этот блок может не работать должным образом.
Блок except
Блок except
запускается, когда блок try
не срабатывает из-за исключения. Инструкции в этом блоке часто дают некоторый контекст того, что пошло не так внутри блока try
.
Если собираетесь перехватить ошибку как исключение, в блоке except
нужно обязательно указать тип этой ошибки. В приведенном выше сниппете место для указания типа ошибки обозначено плейсхолдером <error type>
.
except
можно использовать и без указания типа ошибки. Но лучше так не делать. При таком подходе не учитывается, что возникающие ошибки могут быть разных типов. То есть вы будете знать, что что-то пошло не так, но что именно произошло, какая была ошибка — вам будет не известно.
При попытке выполнить код внутри блока try
также существует вероятность возникновения нескольких ошибок.
Например, вы можете попытаться обратиться к элементу списка по индексу, выходящему за пределы допустимого диапазона, использовать неправильный ключ словаря и попробовать открыть несуществующий файл – и все это внутри одного блока try
.
В результате вы можете столкнуться с IndexError
, KeyError
и FileNotFoundError
. В таком случае нужно добавить столько блоков except
, сколько ошибок ожидается – по одному для каждого типа ошибки.
Блок else
Блок else
запускается только в том случае, если блок try
выполняется без ошибок. Это может быть полезно, когда нужно выполнить ещё какие-то действия после успешного выполнения блока try
. Например, после успешного открытия файла вы можете прочитать его содержимое.
Блок finally
Блок finally
выполняется всегда, независимо от того, что происходит в других блоках. Это полезно, когда вы хотите освободить ресурсы после выполнения определенного блока кода.
Примечание: блоки else
и finally
не являются обязательными. В большинстве случаев вы можете использовать только блок try
, чтобы что-то сделать, и перехватывать ошибки как исключения внутри блока except
.
[python_ad_block]
Итак, теперь давайте используем полученные знания для обработки исключений в Python. Приступим!
Обработка ZeroDivisionError
Рассмотрим функцию divide()
, показанную ниже. Она принимает два аргумента – num
и div
– и возвращает частное от операции деления num/div
.
def divide(num,div): return num/div
Вызов функции с разными аргументами возвращает ожидаемый результат:
res = divide(100,8) print(res) # Output # 12.5 res = divide(568,64) print(res) # Output # 8.875
Этот код работает нормально, пока вы не попробуете разделить число на ноль:
divide(27,0)
Вы видите, что программа выдает ошибку ZeroDivisionError
:
# Output --------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) <ipython-input-19-932ea024ce43> in <module>() ----> 1 divide(27,0) <ipython-input-1-c98670fd7a12> in divide(num, div) 1 def divide(num,div): ----> 2 return num/div ZeroDivisionError: division by zero
Можно обработать деление на ноль как исключение, выполнив следующие действия:
- В блоке
try
поместите вызов функцииdivide()
. По сути, вы пытаетесь разделитьnum
наdiv
(try в переводе с английского — «пытаться», — прим. перев.). - В блоке
except
обработайте случай, когдаdiv
равен 0, как исключение. - В результате этих действий при делении на ноль больше не будет выбрасываться ZeroDivisionError. Вместо этого будет выводиться сообщение, информирующее пользователя, что он попытался делить на ноль.
Вот как все это выглядит в коде:
try: res = divide(num,div) print(res) except ZeroDivisionError: print("You tried to divide by zero :( ")
При корректных входных данных наш код по-прежнему работает великолепно:
divide(10,2) # Output # 5.0
Когда же пользователь попытается разделить на ноль, он получит уведомление о возникшем исключении. Таким образом, программа завершается корректно и без ошибок.
divide(10,0) # Output # You tried to divide by zero :(
Обработка TypeError
В этом разделе мы разберем, как использовать try
и except
для обработки TypeError
в Python.
Рассмотрим функцию add_10()
. Она принимает число в качестве аргумента, прибавляет к нему 10 и возвращает результат этого сложения.
def add_10(num): return num + 10
Вы можете вызвать функцию add_10()
с любым числом, и она будет работать нормально, как показано ниже:
result = add_10(89) print(result) # Output # 99
Теперь попробуйте вызвать функцию add_10()
, передав ей в качестве аргумента не число, а строку.
add_10 ("five")
Ваша программа вылетит со следующим сообщением об ошибке:
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-15-9844e949c84e> in <module>() ----> 1 add_10("five") <ipython-input-13-2e506d74d919> in add_10(num) 1 def add_10(num): ----> 2 return num + 10 TypeError: can only concatenate str (not "int") to str
Сообщение об ошибке TypeError: can only concatenate str (not "int") to str
говорит о том, что можно сложить только две строки, а не добавить целое число к строке.
Обработаем TypeError:
- В блок try мы помещаем вызов функции
add_10()
с my_num в качестве аргумента. Если аргумент допустимого типа, исключений не возникнет. - В противном случае срабатывает блок
except
, в который мы помещаем вывод уведомления для пользователя о том, что аргумент имеет недопустимый тип.
Это показано ниже:
my_num = "five" try: result = add_10(my_num) print(result) except TypeError: print("The argument `num` should be a number")
Поскольку теперь вы обработали TypeError
как исключение, при передаче невалидного аргумента ошибка не возникает. Вместо нее выводится сообщение, что аргумент имеет недопустимый тип.
The argument `num` should be a number
Обработка IndexError
Если вам приходилось работать со списками или любыми другими итерируемыми объектами, вы, вероятно, сталкивались с IndexError
.
Это связано с тем, что часто бывает сложно отслеживать все изменения в итерациях. И вы можете попытаться получить доступ к элементу по невалидному индексу.
В этом примере список my_list
состоит из 4 элементов. Допустимые индексы — 0, 1, 2 и 3 и -1, -2, -3, -4, если вы используете отрицательную индексацию.
Поскольку 2 является допустимым индексом, вы видите, что элемент с этим индексом (C++
) распечатывается:
my_list = ["Python","C","C++","JavaScript"] print(my_list[2]) # Output # C++
Но если вы попытаетесь получить доступ к элементу по индексу, выходящему за пределы допустимого диапазона, вы столкнетесь с IndexError
:
print(my_list[4])
--------------------------------------------------------------------------- IndexError Traceback (most recent call last) <ipython-input-7-437bc6501dea> in <module>() 1 my_list = ["Python","C","C++","JavaScript"] ----> 2 print(my_list[4]) IndexError: list index out of range
Теперь вы уже знакомы с шаблоном, и вам не составит труда использовать try
и except
для обработки данной ошибки.
В приведенном ниже фрагменте кода мы пытаемся получить доступ к элементу по индексу search_idx
.
search_idx = 3 try: print(my_list[search_idx]) except IndexError: print("Sorry, the list index is out of range")
Здесь search_idx = 3
является допустимым индексом, поэтому в результате выводится соответствующий элемент — JavaScript
.
Если search_idx
находится за пределами допустимого диапазона индексов, блок except
перехватывает IndexError
как исключение, и больше нет длинных сообщений об ошибках.
search_idx = 4 try: print(my_list[search_idx]) except IndexError: print("Sorry, the list index is out of range")
Вместо этого отображается сообщение о том, что search_idx
находится вне допустимого диапазона индексов:
Sorry, the list index is out of range
Обработка KeyError
Вероятно, вы уже сталкивались с KeyError
при работе со словарями в Python.
Рассмотрим следующий пример, где у нас есть словарь my_dict
.
my_dict ={"key1":"value1","key2":"value2","key3":"value3"} search_key = "non-existent key" print(my_dict[search_key])
В словаре my_dict
есть 3 пары «ключ-значение»: key1:value1
, key2:value2
и key3:value3
.
Теперь попытаемся получить доступ к значению, соответствующему несуществующему ключу non-existent key
.
Как и ожидалось, мы получим KeyError
:
--------------------------------------------------------------------------- KeyError Traceback (most recent call last) <ipython-input-2-2a61d404be04> in <module>() 1 my_dict ={"key1":"value1","key2":"value2","key3":"value3"} 2 search_key = "non-existent key" ----> 3 my_dict[search_key] KeyError: 'non-existent key'
Вы можете обработать KeyError
почти так же, как и IndexError
.
- Пробуем получить доступ к значению, которое соответствует ключу, определенному
search_key
. - Если
search_key
— валидный ключ, мы распечатываем соответствующее значение. - Если ключ невалиден и возникает исключение — задействуется блок except, чтобы сообщить об этом пользователю.
Все это можно видеть в следующем коде:
try: print(my_dict[search_key]) except KeyError: print("Sorry, that's not a valid key!") # Output: # Sorry, that's not a valid key!
Если вы хотите предоставить дополнительный контекст, например имя невалидного ключа, это тоже можно сделать. Возможно, ключ оказался невалидным из-за ошибки в написании. Если вы укажете этот ключ в сообщении, это поможет пользователю исправить опечатку.
Вы можете сделать это, перехватив невалидный ключ как <error_msg>
и используя его в сообщении, которое печатается при возникновении исключения:
try: print(my_dict[search_key]) except KeyError as error_msg: print(f"Sorry,{error_msg} is not a valid key!")
Обратите внимание, что теперь в сообщении об ошибки указано также и имя несуществующего ключа:
Sorry, 'non-existent key' is not a valid key!
Обработка FileNotFoundError
При работе с файлами в Python часто возникает ошибка FileNotFoundError
.
В следующем примере мы попытаемся открыть файл my_file.txt, указав его путь в функции open()
. Мы хотим прочитать файл и вывести его содержимое.
Однако мы еще не создали этот файл в указанном месте.
my_file = open("/content/sample_data/my_file.txt") contents = my_file.read() print(contents)
Поэтому, попытавшись запустить приведенный выше фрагмент кода, мы получим FileNotFoundError
:
--------------------------------------------------------------------------- FileNotFoundError Traceback (most recent call last) <ipython-input-4-4873cac1b11a> in <module>() ----> 1 my_file = open("my_file.txt") FileNotFoundError: [Errno 2] No such file or directory: 'my_file.txt'
А с помощью try
и except
мы можем сделать следующее:
- Попробуем открыть файл в блоке
try
. - Обработаем
FileNotFoundError
в блокеexcept
, сообщив пользователю, что он попытался открыть несуществующий файл. - Если блок
try
завершается успешно и файл действительно существует, прочтем и распечатаем содержимое. - В блоке
finally
закроем файл, чтобы не терять ресурсы. Файл будет закрыт независимо от того, что происходило на этапах открытия и чтения.
try: my_file = open("/content/sample_data/my_file.txt") except FileNotFoundError: print(f"Sorry, the file does not exist") else: contents = my_file.read() print(contents) finally: my_file.close()
Обратите внимание: мы обработали ошибку как исключение, и программа завершает работу, отображая следующее сообщение:
Sorry, the file does not exist
Теперь рассмотрим случай, когда срабатывает блок else
. Файл my_file.txt теперь присутствует по указанному ранее пути.
Вот содержимое этого файла:
Теперь повторный запуск нашего кода работает должным образом.
На этот раз файл my_file.txt присутствует, поэтому запускается блок else
и содержимое распечатывается, как показано ниже:
Надеемся, теперь вы поняли, как обрабатывать исключения при работе с файлами.
Заключение
В этом руководстве мы рассмотрели, как обрабатывать исключения в Python с помощью try и except.
Также мы разобрали на примерах, какие типы исключений могут возникать и как при помощи except ловить наиболее распространенные ошибки.
Надеемся, вам понравился этот урок. Успехов в написании кода!
Перевод статьи «Python Try and Except Statements – How to Handle Exceptions in Python».
When coding in Python, you can often anticipate runtime errors even in a syntactically and logically correct program. These errors can be caused by invalid inputs or some predictable inconsistencies.
In Python, you can use the try
and the except
blocks to handle most of these errors as exceptions all the more gracefully.
In this tutorial, you’ll learn the general syntax of try
and except
. Then we’ll proceed to code simple examples, discuss what can go wrong, and provide corrective measures using try
and except
blocks.
Syntax of Python Try and Except Blocks
Let’s start by understanding the syntax of the try
and except
statements in Python. The general template is shown below:
try:
# There can be errors in this block
except <error type>:
# Do this to handle exception;
# executed if the try block throws an error
else:
# Do this if try block executes successfully without errors
finally:
# This block is always executed
Let’s look at what the different blocks are used for:
- The
try
block is the block of statements you’d like to try executing. However, there may be runtime errors due to an exception, and this block may fail to work as intended. - The
except
block is triggered when thetry
block fails due to an exception. It contains a set of statements that often give you some context on what went wrong inside thetry
block. - You should always mention the type of error that you intend to catch as exception inside the
except
block, denoted by the placeholder<error type>
in the above snippet. - You might as well use
except
without specifying the<error type>
. But, this is not a recommended practice as you’re not accounting for the different types of errors that can occur.
In trying to execute the code inside the
try
block, there’s also a possibility for multiple errors to occur.
For example, you may be accessing a list using an index that’s way out of range, using a wrong dictionary key, and trying to open a file that does not exist — all inside the try
block.
In this case, you may run into IndexError
, KeyError
, and FileNotFoundError
. And you have to add as many except
blocks as the number of errors that you anticipate, one for each type of error.
- The
else
block is triggered only if thetry
block is executed without errors. This can be useful when you’d like to take a follow-up action when thetry
block succeeds. For example, if you try and open a file successfully, you may want to read its content. - The
finally
block is always executed, regardless of what happens in the other blocks. This is useful when you’d like to free up resources after the execution of a particular block of code.
Note: The
else
andfinally
blocks are optional. In most cases, you can use only thetry
block to try doing something, and catch errors as exceptions inside theexcept
block.
Over the next few minutes, you’ll use what you’ve learned thus far to handle exceptions in Python. Let’s get started.
How to Handle a ZeroDivisionError
in Python
Consider the function divide()
shown below. It takes two arguments – num
and div
– and returns the quotient of the division operation num/div
.
def divide(num,div):
return num/div
▶ Calling the function with different numbers returns results as expected:
res = divide(100,8)
print(res)
# Output
12.5
res = divide(568,64)
print(res)
# Output
8.875
This code works fine until you try dividing by zero:
divide(27,0)
You see that the program crashes throwing a ZeroDivisionError
:
# Output
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
<ipython-input-19-932ea024ce43> in <module>()
----> 1 divide(27,0)
<ipython-input-1-c98670fd7a12> in divide(num, div)
1 def divide(num,div):
----> 2 return num/div
ZeroDivisionError: division by zero
You can handle this division by zero as an exception by doing the following:
- In the
try
block, place a call to thedivide()
function. In essence, you’re trying to dividenum
bydiv
. - Handle the case when
div
is0
as an exception inside theexcept
block. - In this example, you can except
ZeroDivisionError
by printing a message informing the user that they tried dividing by zero.
This is shown in the code snippet below:
try:
res = divide(num,div)
print(res)
except ZeroDivisionError:
print("You tried to divide by zero :( ")
With a valid input, the code still works fine.
divide(10,2)
# Output
5.0
When you try diving by zero, you’re notified of the exception that occurs, and the program ends gracefully.
divide(10,0)
# Output
You tried to divide by zero :(
How to Handle a TypeError
in Python
In this section, you’ll see how you can use try
and except
to handle a TypeError
in Python.
▶ Consider the following function add_10()
that takes in a number as the argument, adds 10 to it, and returns the result of this addition.
def add_10(num):
return num + 10
You can call the function add_10()
with any number and it’ll work fine, as shown below:
result = add_10(89)
print(result)
#Output
99
Now try calling add_10()
with "five"
instead of 5
.
add_10("five")
You’ll notice that your program crashes with the following error message:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-15-9844e949c84e> in <module>()
----> 1 add_10("five")
<ipython-input-13-2e506d74d919> in add_10(num)
1 def add_10(num):
----> 2 return num + 10
TypeError: can only concatenate str (not "int") to str
The error message TypeError: can only concatenate str (not "int") to str
explains that you can only concatenate two strings, and not add an integer to a string.
Now, you have the following:
- Given a number
my_num
, try calling the functionadd_10()
withmy_num
as the argument. If the argument is of valid type, there’s no exception - Otherwise, the
except
block corresponding to theTypeError
is triggered, notifying the user that the argument is of invalid type.
This is explained below:
my_num = "five"
try:
result = add_10(my_num)
print(result)
except TypeError:
print("The argument `num` should be a number")
Since you’ve now handled TypeError
as an exception, you’re only informed that the argument is of invalid type.
The argument `num` should be a number
How to Handle an IndexError
in Python
If you’ve worked with Python lists, or any Python iterable before, you’ll have probably run into IndexError
.
This is because it’s often difficult to keep track of all changes to iterables. And you may be trying to access an item at an index that’s not valid.
▶ In this example, the list my_list
has 4 items. The valid indices are 0, 1, 2, and 3, and -1, -2, -3, -4 if you use negative indexing.
As 2
is a valid index, you see that the item at index 2
, which is C++
, is printed out:
my_list = ["Python","C","C++","JavaScript"]
print(my_list[2])
#Output
C++
If you try accessing an item at index that’s outside the range of valid indices, you’ll run into an IndexError
:
print(my_list[4])
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
<ipython-input-7-437bc6501dea> in <module>()
1 my_list = ["Python","C","C++","JavaScript"]
----> 2 print(my_list[4])
IndexError: list index out of range
If you’re familiar with the pattern, you’ll now use try
and except
to handle index errors.
▶ In the code snippet below, you try accessing the item at the index specified by search_idx
.
search_idx = 3
try:
print(my_list[search_idx])
except IndexError:
print("Sorry, the list index is out of range")
Here, the search_idx
(3
) is a valid index, and the item at the particular index is printed out:
JavaScript
If the search_idx
is outside the valid range for indices, the except block catches the IndexError
as an exception, and there are no more long error messages. 🙂
search_idx = 4
try:
print(my_list[search_idx])
except IndexError:
print("Sorry, the list index is out of range")
Rather, the message that the search_idx
is out of the valid range of indices is displayed:
Sorry, the list index is out of range
How to Handle a KeyError
in Python
You have likely run into KeyError
when working with Python dictionaries
▶ Consider this example where you have a dictionary my_dict
.
my_dict ={"key1":"value1","key2":"value2","key3":"value3"}
search_key = "non-existent key"
print(my_dict[search_key])
- The dictionary
my_dict
has 3 key-value pairs,"key1:value1"
,"key2:value2"
, and"key3:value3"
- Now, you try to tap into the dictionary and access the value corresponding to the key
"non-existent key"
.
As expected, you’ll get a KeyError
:
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-2-2a61d404be04> in <module>()
1 my_dict ={"key1":"value1","key2":"value2","key3":"value3"}
2 search_key = "non-existent key"
----> 3 my_dict[search_key]
KeyError: 'non-existent key'
You can handle KeyError
in almost the same way you handled IndexError
.
- You can try accessing the value corresponding to the key specified by the
search_key
. - If
search_key
is indeed a valid key, the corresponding value is printed out. - If you run into an exception because of a non-existent key, you use the
except
block to let the user know.
This is explained in the code snippet below:
try:
print(my_dict[search_key])
except KeyError:
print("Sorry, that's not a valid key!")
Sorry, that's not a valid key!
▶ If you want to provide additional context such as the name of the invalid key, you can do that too. It’s possible that the key was misspelled which made it invalid. In this case, letting the user know the key used will probably help them fix the typo.
You can do this by catching the invalid key as <error_msg>
and use it in the message printed when the exception occurs:
try:
print(my_dict[search_key])
except KeyError as error_msg:
print(f"Sorry,{error_msg} is not a valid key!")
▶ Notice how the name of the key is also printed out:
Sorry,'non-existent key' is not a valid key!
How to Handle a FileNotFoundError
in Python
Another common error that occurs when working with files in Python is the FileNotFoundError
.
▶ In the following example, you’re trying to open the file my_file.txt
by specifying its path to the function open()
. And you’d like to read the file and print out the contents of the file.
However, you haven’t yet created the file in the specified location.
If you try running the code snippet below, you’ll get a FileNotFoundError
:
my_file = open("/content/sample_data/my_file.txt")
contents = my_file.read()
print(contents)
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
<ipython-input-4-4873cac1b11a> in <module>()
----> 1 my_file = open("my_file.txt")
FileNotFoundError: [Errno 2] No such file or directory: 'my_file.txt'
And using try
and except
, you can do the following:
- Try opening the file in the
try
block. - Handle
FileNotFoundError
in theexcept
block by letting the user know that they tried to open a file that doesn’t exist. - If the
try
block succeeds, and the file does exist, read and print out the contents of the file. - In the
finally
block, close the file so that there’s no wastage of resources. Recall how the file will be closed regardless of what happens in the file opening and reading steps.
try:
my_file = open("/content/sample_data/my_file.txt")
except FileNotFoundError:
print(f"Sorry, the file does not exist")
else:
contents = my_file.read()
print(contents)
finally:
my_file.close()
Notice how you’ve handled the error as an exception and the program ends gracefully displaying the message below:
Sorry, the file does not exist
▶ Let’s consider the case in which the else
block is triggered. The file my_file.txt
is now present at the path mentioned earlier.
And here’s what the file my_file.txt
contains:
Now, re-running the earlier code snippet works as expected.
This time, the file my_file.txt
is present, the else
block is triggered and its contents are printed out, as shown below:
I hope this clarifies how you can handle exceptions when working with files.
Conclusion
In this tutorial, you’ve learned how you can use try
and except
statements in Python to handle exceptions.
You coded examples to understand what types of exception may occur and how you can use except
to catch the most common errors.
Hope you enjoyed this tutorial. Happy coding! Until next time
Learn to code for free. freeCodeCamp’s open source curriculum has helped more than 40,000 people get jobs as developers. Get started