Отсоединить аккумулятор для сброса ошибок

Here are some notes that you might want use to craft your solution:

The easiest way to retrieve a couroutine’s exception (or result!) is to await for it. asyncio.gather() will create tasks from coroutines and wrap all of them in one encompassing task that will fail if one of the subtasks fails:

import asyncio

import random


async def coro(n):
    print("Start", n)
    await asyncio.sleep(random.uniform(0.2, 0.5))
    if n % 4 == 0:
        raise Exception('fail ({})'.format(n))
    return "OK: {}".format(n)


async def main():
    tasks = [coro(i) for i in range(10)]
    await asyncio.gather(*tasks)
    print("done")

loop = asyncio.get_event_loop()
try:
    asyncio.ensure_future(main())
    loop.run_forever()
finally:
    loop.close()

This however does not shutdown the loop. To stop a running loop, use loop.stop(). Use this instead:

async def main():
    tasks = [coro(i) for i in range(10)]
    try:
        await asyncio.gather(*tasks)
    except Exception as e:
        loop.stop()
        raise
    print("done")

Stopping the loop while some long-running coroutines are running is probably not what you want. You might want to first signal some your coroutines to shut down using an event:

import asyncio

import random


async def repeat(n):
    print("start", n)
    while not shutting_down.is_set():
        print("repeat", n)
        await asyncio.sleep(random.uniform(1, 3))
    print("done", n)


async def main():
    print("waiting 6 seconds..")
    await asyncio.sleep(6)
    print("shutting down")
    shutting_down.set()  # not a coroutine!
    print("waiting")
    await asyncio.wait(long_running)
    print("done")
    loop.stop()

loop = asyncio.get_event_loop()
shutting_down = asyncio.Event(loop=loop)
long_running = [loop.create_task(repeat(i + 1))  for i in range(5)]
try:
    asyncio.ensure_future(main())
    loop.run_forever()
finally:
    loop.close()

If you don’t want to await for your tasks, you might want to use an asyncio.Event (or asyncio.Queue) to signal a global error handler to stop the loop:

import asyncio


async def fail():
    try:
        print("doing stuff...")
        await asyncio.sleep(0.2)
        print("doing stuff...")
        await asyncio.sleep(0.2)
        print("doing stuff...")
        raise Exception('fail')
    except Exception as e:
        error_event.payload = e
        error_event.set()
        raise  # optional


async def error_handler():
    await error_event.wait()
    e = error_event.payload
    print("Got:", e)
    raise e


loop = asyncio.get_event_loop()
error_event = asyncio.Event()
try:
    loop.create_task(fail())
    loop.run_until_complete(error_handler())
finally:
    loop.close()

(Used here with run_until_complete() for simplicity, but can be used with loop.stop() as well)

I have a code that stops running each time there is an error.
Is there a way to add a code to the script which will ignore all errors and keep running the script until completion?

Below is the code:

import sys
import tldextract

def main(argv):

        in_file = argv[1]
        f = open(in_file,'r')
        urlList = f.readlines()
        f.close()
        destList = []

        for i in urlList:
            print i
            str0 = i
            for ch in ['\n','\r']:
                    if ch in str0:
                        str0 = str0.replace(ch,'')
            str1 = str(tldextract.extract(str0))

            str2 = i.replace('\n','') + str1.replace("ExtractResult",":")+'\n'
            destList.append(str2)

        f = open('destFile.txt','w')
        for i in destList:
                f.write(i)

        f.close()

        print "Completed successfully:"


if __name__== "__main__":
    main(sys.argv)

Many thanks

asked Jul 22, 2015 at 12:38

Remo's user avatar

7

You should always ‘try’ to open files. This way you can manage exceptions, if the file does not exist for example. Take a loot at Python Tutorial Exeption Handling

import sys

try:
    f = open('myfile.txt')
    s = f.readline()
    i = int(s.strip())
except IOError as e:
    print "I/O error({0}): {1}".format(e.errno, e.strerror)
except ValueError:
    print "Could not convert data to an integer."
except:
    print "Unexpected error:", sys.exc_info()[0]
    raise

or

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

Do not(!) just ‘pass’ in the exception block. This will(!) make you fall on your face even harder.

answered Jul 22, 2015 at 12:43

kiigass's user avatar

kiigasskiigass

4271 gold badge4 silver badges16 bronze badges

1

Where ever your error(s) is happening you can wrap it in a try/except block

for i in loop:
    try:
        code goes here...
    except:
        pass

answered Jul 22, 2015 at 12:41

Austin's user avatar

AustinAustin

4,3066 gold badges40 silver badges52 bronze badges

Stop and wait protocol is an error control protocol, in this protocol the sender sends data packets one at a time and waits for positive acknowledgment from the receiver’s side, if acknowledgment is received then the sender sends the next data packet else it’ll resend the previous packet until a positive acknowledgment is not received. 

Note: To get more info on what is stop and wait protocol, refer Stop and Wait ARQ article.

CRC aka Cyclic redundancy check is an error detection mechanism, its procedure is as follows.

Sender side

  • Choose a generator polynomial mutually agreed upon by the sender and receiver, let k be the number of bits in the key obtained from this polynomial.
  • Append (k – 1) 0’s to the right of the actual binary data.
  • Divide the data obtained in step 2 by the key, and store the remainder.
  • Append the remainder to the actual binary data, and send the data packet.

Receiver side

  • Divide the received data bits by the key.
  • If the remainder is non-zero, then the data is corrupted.

In this article, we will implement stop and wait algorithm such that the sender computes error detecting code using CRC and sends the data along with error detecting code to the receiver using socket. The receiver, after ensuring an error-free frame, saves the data.

The procedure will be as follows:

At sender

  • Read input from a file, n characters at a time.
  • Convert it into binary
  • Generate its CRC.
  • Send data + CRC bits to the receiver, but before sending, randomly introduce error

At receiver:

  • Receive data packet.
  • Determine if it is error-free. If yes extract data bits, convert them into character form, and save them into the output file. Send Ack as OK. If not, send NAK.

At sender

  • If OK is received, proceed with the next n characters. Otherwise, if NAK is received, send the data + CRC bits again to the receiver after randomly introducing the error.

Final outcome: Input and output files should match. A log file should show how many frames were in error and how many retries were done for each frame. 

Error Generation 

Error is introduced as follows :

  • Generate a random number, say r1. Perform r1 % 2. If you get a 0 do not introduce error and send original bits. If you get a 1, introduce error.
  • To decide which bit will be in error, generate another random number, say r2. Perform r2 %(size of received frame). Assume you get a value i as the outcome. Flip the i-th bit. Now send it to the receiver.

Save this file as error_gen.py.

Python3

import random

class err_gen() :

    def induce_err(self, in_str) :

        chk = (int)(random.random() * 1000) % 2

        if not chk :

            return in_str

        idx = (int)(random.random() * 1000) % len(in_str)

        f_bit = '*'

        if in_str[idx] == '0':

            f_bit = '1'

        else :

            f_bit = '0'

        out_str = in_str[ : idx] + f_bit + in_str[idx + 1 : ]

        return out_str

if __name__ == "__main__":

    data = "1001010"

    print("Initial : ", data)

    print("Final : ", err_gen().induce_err(data))

Output:

Initial :  1001010
Final :  0001010

Modulo two Division

Modulo two-division is required to calculate the CRC. Explaining the working of this code is beyond the scope of this article, the curious ones can refer Modulo two division.

Save this file as calc.py.

Python3

def xor(a, b):

    result = []

    for i in range(1, len(b)):

        if a[i] == b[i]:

            result.append('0')

        else:

            result.append('1')

    return ''.join(result)

def mod2div(dividend, divisor):

    pick = len(divisor)

    tmp = dividend[0: pick]

    while pick < len(dividend):

        if tmp[0] == '1':

            tmp = xor(divisor, tmp) + dividend[pick]

        else:

            tmp = xor('0'*pick, tmp) + dividend[pick]

        pick += 1

    if tmp[0] == '1':

        tmp = xor(divisor, tmp)

    else:

        tmp = xor('0'*pick, tmp)

    return tmp

if __name__ == "__main__":

    dividend = "10010101"

    divisor = "011010"

    print(dividend + " % " + divisor + " = " + mod2div(dividend, divisor))

Output:

10010101 % 011010 = 01001

Configuration Data

These are some constants that are shared by the receiver and the sender save this file as configuration.py.

Python3

FRAME_SIZE = 10

BUFFER_SIZE = 1024 * 10

END_OF_FILE = "##**##**##**##**##"

CRC_GENERATOR = "10110100110101110011010101110100000101"

REJECT = "NAK"

ACCEPT = "OK"

Client

The client class will have five methods.

  • Constructor: To connect to the server using a socket at the given IP address and port number.
  • asciiToBin: To convert ASCII string to binary string.
  • appendZero: To append (k – 1) 0’s to the end of binary data.
  • encode: To generate and append CRC at the end of actual data bits.
  • sendfile
    • This method reads n characters from the input file at a time &
    • Creates the data packet to be sent by calling the encode method.
    • Calls induce_error method to randomly introduce an error in the data packet.
    • Sends the data packet and waits for the acknowledgment.
    • If the acknowledgment received is positive, then move on to the next n bits,
    • else resend the current data packet.
    • When the file is completely read, then send a flag to tell the receiver to stop waiting for the next frame.
    • Terminate the session.

Save this file as CLIENT.py.

Python3

from calc import mod2div

from error_gen import err_gen

from configuration import *

import socket

class Client:

    def __init__(self, ipadd, portn):

        self.socket_ = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

        self.socket_.connect((ipadd, portn))

    def asciiToBin(self, data):

        return bin(int.from_bytes(data.encode(), 'big'))

    def appendZero(self, message):

        message = (message.ljust(len(CRC_GENERATOR) - 1 + len(message), '0'))

        return message

    def encode(self, data):

        message = self.asciiToBin(data)

        dividend = self.appendZero(message)

        crc = mod2div(dividend, CRC_GENERATOR)

        curr_frame = (message + crc)

        return curr_frame

    def send_file(self, filename='file.txt'):

        f = open(filename)

        data = f.read(FRAME_SIZE)

        while len(data) > 0:

            curr_frame = self.encode(data)

            curr_frame = err_gen().induce_err(curr_frame)

            self.socket_.send(curr_frame.encode())

            if self.socket_.recv(BUFFER_SIZE).decode() == 'OK':

                data = f.read(FRAME_SIZE)

        self.socket_.send(END_OF_FILE.encode())

        self.socket_.close()

        f.close()

        print("File sent")

newclient = Client(ipadd="127.0.0.1", portn=3241)

newclient.send_file(filename="file.txt")

Server

The server class will have six methods.

  • Constructor: To listen for client request at the given IP address and port number.
  • iszero : To determine if a string represents 0.
  • isCurrupted : To determine if the received data is corrupted by performing modulo 2 division by the CRC_GENERATOR.
  • decode : To extract the data bits from the received data packet and convert them to their ASCII values.
  • log : To log the entry of each frame in the logfile.
  • receive_file
    • This method receives the data packet from the sender &
    • Checks its validity by calling the isCurrupted function.
    • If the data packet is valid, then it decodes it and copies it in a file at server’s end and sends a positive acknowledgement to the sender and logs the entry of the data packet in the logfile.
    • else, it sends a negative acknowledgement to the sender.
    • If the received data packet is the end of file, then it terminates all the connections and returns.

Save this file as SERVER.py

Python3

import socket

from calc import mod2div

from configuration import *

class Server:

    def __init__(self, ipaddr, portn):

        self.socket_ = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

        self.socket_.bind((ipaddr, portn))

        self.socket_.listen(5)

    def iszero(self, data):

        for x in data:

            if x != '0':

                return False

        return True

    def isCurrupted(self, message):

        return not self.iszero(mod2div(message, CRC_GENERATOR))

    def decode(self, message):

        message = message[: 1 - len(CRC_GENERATOR)]

        n = int(message, 2)

        return n.to_bytes((n.bit_length() + 7) // 8, 'big').decode()

    def log(self, loghandle, itr, received_frame, retrie_count):

        loghandle.write("Frame Number : " + str(itr) + "\n")

        loghandle.write("Frame Content : \"" +

                        self.decode(received_frame) + "\"\n")

        loghandle.write("Retries : " + str(retrie_count) + "\n\n")

    def receive_file(self, filepath, logpath):

        received_socket, addr = self.socket_.accept()

        f = open(filepath, 'w')

        l = open(logpath, 'w')

        itr = 1

        retrie_count = 0

        while 1:

            received_frame = received_socket.recv(BUFFER_SIZE).decode()

            if received_frame == END_OF_FILE:

                f.close()

                l.close()

                self.socket_.close()

                print("File Received")

                return

            if self.isCurrupted(received_frame):

                retrie_count += 1

                received_socket.send(REJECT.encode())

            else:

                f.write(self.decode(received_frame))

                self.log(l, itr, received_frame, retrie_count)

                retrie_count = 0

                received_socket.send(ACCEPT.encode())

newServer = Server(ipaddr="127.0.0.1", portn=3241)

newServer.receive_file(filepath="received_data.txt", logpath="logfile.txt")

To see the execution of the code, keep all the files in a single directory, and execute the server file before executing the client’s file. Also, make sure that you have the input text file and its correct path.

Execution:

Python Program for Stop & Wait Implementation using CRC

Program execution process

Last Updated :
21 Dec, 2022

Like Article

Save Article

() translation by (you can also view the original English article)

В этом уроке вы узнаете, как обрабатывать ошибки в Python со всех возможных точек зрения. Обработка ошибок является важнейшим аспектом проектирования, и она проходит от самых низких уровней (иногда это аппаратное обеспечение) через весь путь к конечным пользователям. Если у вас нет продуманной стратегии в этой области, ваша система будет ненадежной, а пользовательский опыт будет плохим, и вы будете иметь много проблем с отладкой и устранением неполадок.

Ключ к успеху, зная обо всех связанных сторонах вопроса, рассматривать их целостно и сформировать решение, которое учитывает каждый аспект.

Коды статуса против Исключений

Существует две основных модели обработок ошибок: Коды статуса и Исключения. Коды статуса могут использоваться в любом языке программирования. Исключения требуют поддержки языка/среды исполнения.

Python поддерживает исключения. Python и его стандартная библиотека использует исключения свободно для того, чтобы сообщить о различных состояниях, таких как IO ошибки, ошибки деления на ноль, ошибки пределов индексации, а также некоторые некритичные ситуаций, такие как конец итерации (хотя обычно эти ошибки скрыты). Большинство библиотек придерживаются этого принципа и вызывают исключения.

Это означает, что ваш код будет обрабатывать исключения, возникающие в Python или в библиотеке, во всяком случае, так что вы можете также вызывать исключения напрямую из вашего кода, когда это необходимо и не полагаться на коды статуса.

Небольшой пример

Прежде чем погрузиться в святая святых лучших практик обработки исключений и ошибок Python, давайте рассмотрим некоторые исключения в действии:

1
def f():
2

3
    return 4 / 0
4

5

6

7
def g():
8

9
    raise Exception("Don't call us. We'll call you")
10

11

12

13
def h():
14

15
    try:
16

17
        f()
18

19
    except Exception as e:
20

21
        print(e)
22

23
    try:
24

25
        g()
26

27
    except Exception as e:
28

29
        print(e)

При вызове h(), мы получаем на выходе:

1
h()
2

3
division by zero
4

5
Don't call us. We'll call you

Исключения Python 

Python исключениями являются объекты организованные в классовой иерархии.

Вот иерархия целиком:

1
BaseException
2

3
 +-- SystemExit
4

5
 +-- KeyboardInterrupt
6

7
 +-- GeneratorExit
8

9
 +-- Exception
10

11
      +-- StopIteration
12

13
      +-- StandardError
14

15
      |    +-- BufferError
16

17
      |    +-- ArithmeticError
18

19
      |    |    +-- FloatingPointError
20

21
      |    |    +-- OverflowError
22

23
      |    |    +-- ZeroDivisionError
24

25
      |    +-- AssertionError
26

27
      |    +-- AttributeError
28

29
      |    +-- EnvironmentError
30

31
      |    |    +-- IOError
32

33
      |    |    +-- OSError
34

35
      |    |         +-- WindowsError (Windows)
36

37
      |    |         +-- VMSError (VMS)
38

39
      |    +-- EOFError
40

41
      |    +-- ImportError
42

43
      |    +-- LookupError
44

45
      |    |    +-- IndexError
46

47
      |    |    +-- KeyError
48

49
      |    +-- MemoryError
50

51
      |    +-- NameError
52

53
      |    |    +-- UnboundLocalError
54

55
      |    +-- ReferenceError
56

57
      |    +-- RuntimeError
58

59
      |    |    +-- NotImplementedError
60

61
      |    +-- SyntaxError
62

63
      |    |    +-- IndentationError
64

65
      |    |         +-- TabError
66

67
      |    +-- SystemError
68

69
      |    +-- TypeError
70

71
      |    +-- ValueError
72

73
      |         +-- UnicodeError
74

75
      |              +-- UnicodeDecodeError
76

77
      |              +-- UnicodeEncodeError
78

79
      |              +-- UnicodeTranslateError
80

81
      +-- Warning
82

83
           +-- DeprecationWarning
84

85
           +-- PendingDeprecationWarning
86

87
           +-- RuntimeWarning
88

89
           +-- SyntaxWarning
90

91
           +-- UserWarning
92

93
           +-- FutureWarning
94

95
  +-- ImportWarning
96

97
  +-- UnicodeWarning
98

99
  +-- BytesWarning
100
 

Существует несколько специальных исключений, которые являются производными от BaseException, такие как SystemExit, KeyboardInterrupt и GeneratorExit. Еще есть класс Exception, который является базовым классом для StopIteration, StandardError и Warning. Все стандартные ошибки являются производными от StandardError.

Когда вы получаете исключение или функция, которую вы выполнили вызывает исключение, обычный порядок кода завершается и исключение начинает распространятся вверх по стеку вызовов до тех пор, пока не встречает обработчик соответствующих исключений. Если обработчик не доступен, процесс (или, точнее, текущий поток) будет прекращен с сообщением о необработанном исключении.

Вызов исключений

Вызов исключений очень прост. Вы просто используете ключевое слово raise чтобы вызвать объект, который является подклассом Exception. Это может быть экземпляр Exception, одно из стандартных исключений (напр., RuntimeError), или подкласс Exception, который вы получили. Вот небольшой фрагмент кода, который демонстрирует эти случаи:

1
# Raise an instance of the Exception class itself

2

3
raise Exception('Ummm... something is wrong')
4

5

6

7
# Raise an instance of the RuntimeError class

8

9
raise RuntimeError('Ummm... something is wrong')
10

11

12

13
# Raise a custom subclass of Exception that keeps the timestamp the exception was created

14

15
from datetime import datetime
16

17

18

19
class SuperError(Exception):
20

21
    def __init__(self, message):
22

23
        Exception.__init__(message)
24

25
        self.when = datetime.now()
26

27

28

29

30

31
raise SuperError('Ummm... something is wrong')

Перехват исключений

Вы получили исключение, с условием except, как вы видели в примере. Когда вы получили исключение, у вас есть три варианта:

  • Пропустить (обработать его и продолжить работу).
  • Сделать что-то вроде записи в журнал, но получить повторно то же самое исключение, чтобы продолжить его обработку на более высоком уровне.
  • Вызвать другое исключение вместо текущего.

Пропустить исключение

Если вы знаете, как его обработать и как его полностью восстановить, можно пропустить исключение.

Например, если вы получаете входящий файл, который может быть в различных форматах (JSON, YAML), вы можете попробовать проанализировать его с помощью различных средств. Если анализатор JSON создаёт исключение, которое показывает, что файл имеет некорректный формат JSON, вы пропускаете его и пробуете проанализировать через парсер YAML. Если парсер YAML также не справляется с задачей, тогда вы даёте исключению перейти на следующий уровень.

1
import json
2

3
import yaml
4

5

6

7
def parse_file(filename):
8

9
    try:
10

11
        return json.load(open(filename))
12

13
    except json.JSONDecodeError
14

15
        return yaml.load(open(filename))

Обратите внимание, что другие исключения (например, file not found или no read permissions) будут переходить на следующий уровень и не будут обработаны конкретным исключением. Это хорошая тактика в том случае, если вы хотите использовать YAML парсер, когда анализ с помощью JSON парсера не удался. 

Если вы хотите обрабатывать все исключения, тогда используйте except Exception. Например:

1
def print_exception_type(func, *args, **kwargs):
2

3
    try:
4

5
        return func(*args, **kwargs)
6

7
    except Exception as e:
8

9
        print type(e)

Обратите внимание, что, добавляя as e, вы привязываете объект к имении e в вашем исключении.

Перезапуск исключения

Чтобы перезапустить исключение, просто напишите raise без аргументов внутри обработчика. Это позволит выполнить некоторую локальную обработку, но также пропустит исключение для обработки на верхние уровни. Здесь, функция invoke_function() выводит тип исключения в консоль и затем повторно вызывает его.

1
def invoke_function(func, *args, **kwargs):
2

3
    try:
4

5
        return func(*args, **kwargs)
6

7
    except Exception as e:
8

9
        print type(e)
10

11
        raise

Вызов Различных Исключений

Есть несколько случаев, когда вы хотели бы вызвать другое исключение. Иногда вы хотите сгруппировать несколько различных низкоуровневых исключений в одну категорию, которая равномерно обрабатывается на более высоком уровне кода. В других случаях вам нужно преобразовать исключение на уровне пользователя и предоставить контекст конкретного приложения.

Финальное утверждение

Иногда вы хотите убедиться, что код очистки выполняется, даже если где-то по пути возникло исключение. Например, у вас может быть подключение к базе данных, которое требуется закрыть, как только вы закончите. Это неправильный способ сделать это:

1
def fetch_some_data():
2

3
    db = open_db_connection()
4

5
    query(db)
6

7
    close_db_Connection(db)

Если функция query() вызывает исключение, то вызов close_db_connection() никогда не будет выполнен и подключение останется открытым. Утверждение finally всегда выполняется после всех попыток обработчика. Вот как сделать это правильно:

1
def fetch_some_data():
2

3
    db = None
4

5
    try:
6

7
        db = open_db_connection()
8

9
        query(db)
10

11
    finally:
12

13
        if db is not None:
14

15
            close_db_connection(db)

Вызов open_db_connection() может не вернуть подключение или вызвать исключение. В этом случае нет необходимости закрывать соединение.

При использовании finally, вы должны быть осторожны, чтобы не вызвать другие исключения, потому, что они скроют исходное.

Диспетчеров Контекста

Контекстные менеджеры обеспечивают еще один механизм обработки ресурсов, таких как файлы или подключения к БД, которые выполняются автоматически, даже если исключения были вызваны. Вместо блоков try-finally, можно использовать определение with. Вот пример с файлом:

1
def process_file(filename):
2

3
     with open(filename) as f:
4

5
        process(f.read())

Теперь, даже если process() вызывает исключение, этот файл будет закрыт правильно сразу же когда область видимости блока with завершена, независимо от того, было исключение обработано или нет.

Ведение журнала

Ведение журнала обычно требуется в нетривиальных, масштабных системах. Это особенно полезно в веб-приложениях, где вы можете исправить все исключения универсальным способом: Просто записать в журнал исключение и вернуть сообщение об ошибке.

При записи полезно учитывать тип исключения, сообщение и маршрут ошибки. Вся эта информация доступна через объект sys.exc_info, но если вы используете logger.exception() метод в обработчике исключений, Python извлечёт всю необходимую для вас информацию.

Это лучший пример, которую я рекомендую:

1
import logging
2

3
logger = logging.getLogger()
4

5

6

7
def f():
8

9
    try:
10

11
        flaky_func()
12

13
    except Exception:
14

15
        logger.exception()
16

17
        raise

Если вы будете придерживаться этому шаблону,тогда (предполагаю, что вы настроили запись в журнал правильно), независимо от того, что происходит, вы будете иметь очень понятные записи в ваших журналах о что пошло не так, и будете иметь возможность исправить проблему.

Если вы повторно вызываете исключение, убедитесь что вы не записывайте в журнал повторно одну и туже ошибку на разных уровнях. Эту будет бесполезный мусор. который может запутать вас и заставить думать что произошло несколько ошибок, хотя на самом деле одна ошибка была зарегистрирована несколько раз.

Самый простой способ сделать это заключается в том, чтобы позволить всем исключениям переходить дальше (если они могут быть обработаны и пропущены ранее), и затем выполнить запись в журнал на самом верхнем уровне системы/приложения.

Sentry

Ведение журнала это возможность. Наиболее распространенные реализации которой, является использование журнала. Но, для крупномасштабных распределенных систем с сотнями, тысячами или более серверов, это не всегда лучшее решение.

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

Работа с временной ошибкой

Некоторые ошибки являются временными, в частности при работе с распределенными системами. Система, которая начинает ругаться при первом признаке ошибки не очень полезна.

Если ваш код получает доступ к удаленной системе, которая не отвечает, традиционное решение, это таймауты, но иногда случается не каждая система разработана с таймаутами. Таймауты, не всегда удобны для калибровки при изменении условий.

Другой подход заключается в том, чтобы быстро получить ошибку и затем повторить попытку. Преимущество в том, что если цель реагирует быстро, то вам не придется тратить много времени находясь в режиме ожидания и можно реагировать незамедлительно. Но если это сделать не удалось, вы можете повторять запрос несколько раз до тех пор, пока вы не решите, что ресурс действительно недоступен и вызовете исключение. В следующем разделе я расскажу об оформителе, который может сделать это для вас.

Полезные оформители

Два оформителя которые могут помочь в обработке ошибок, это @log_error, который записывает исключение и затем вновь вызывает его и @retry оформитель, который будет повторять вызов функции несколько раз.

Журнал ошибок

Вот пример простой реализации. Оформитель исключает объект logger. Когда он оформляет функцию и функция вызвана, он обработает вызов в блоке try-except, и если там было исключение сделает запись в журнал и наконец повторно вызовет исключение.

1
def log_error(logger)
2

3
    def decorated(f):
4

5
        @functools.wraps(f)
6

7
        def wrapped(*args, **kwargs):
8

9
            try:
10

11
                return f(*args, **kwargs)
12

13
            except Exception as e:
14

15
                if logger:
16

17
                    logger.exception(e)
18

19
                raise
20

21
        return wrapped
22

23
    return decorated

Вот пример, как его использовать:

1
import logging
2

3
logger = logging.getLogger()
4

5

6

7
@log_error(logger)
8

9
def f():
10

11
    raise Exception('I am exceptional')

Retrier

Здесь, очень хорошая реализация @retry оформителя.

1
import time
2

3
import math
4

5

6

7
# Retry decorator with exponential backoff

8

9
def retry(tries, delay=3, backoff=2):
10

11
  '''Retries a function or method until it returns True.

12


13


14


15
  delay sets the initial delay in seconds, and backoff sets the factor by which

16


17
  the delay should lengthen after each failure. backoff must be greater than 1,

18


19
  or else it isn't really a backoff. tries must be at least 0, and delay

20


21
  greater than 0.'''
22

23

24

25
  if backoff <= 1:
26

27
    raise ValueError("backoff must be greater than 1")
28

29

30

31
  tries = math.floor(tries)
32

33
  if tries < 0:
34

35
    raise ValueError("tries must be 0 or greater")
36

37

38

39
  if delay <= 0:
40

41
    raise ValueError("delay must be greater than 0")
42

43

44

45
  def deco_retry(f):
46

47
    def f_retry(*args, **kwargs):
48

49
      mtries, mdelay = tries, delay # make mutable

50

51

52

53
      rv = f(*args, **kwargs) # first attempt

54

55
      while mtries > 0:
56

57
        if rv is True: # Done on success

58

59
          return True
60

61

62

63
        mtries -= 1      # consume an attempt

64

65
        time.sleep(mdelay) # wait...

66

67
        mdelay *= backoff  # make future wait longer

68

69

70

71
        rv = f(*args, **kwargs) # Try again

72

73

74

75
      return False # Ran out of tries :-(

76

77

78

79
    return f_retry # true decorator -> decorated function

80

81
  return deco_retry  # @retry(arg[, ...]) -> true decorator

Заключение

Обработка ошибок имеет решающее значение для пользователей и разработчиков. Python предоставляет отличную поддержку на уровне языка и стандартной библиотеки для обработки ошибок на основе исключений. Следуя рекомендациям старательно, вы можете преодолеть этот аспект, которым так часто пренебрегают.

Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Тестирование ошибок

Python: Продвинутое тестирование

В этом уроке мы начнем погружаться в тестирование и обсудим нюансы, которые возникают в тестировании приложений.

Основные тесты, которые нужно писать — это тесты на успешные сценарии работы. Но в некоторых ситуациях код должен возвращать ошибки, и их тоже бывает нужно проверять. Под ошибками понимаются ситуации, в которых код выбрасывает исключение.

Посмотрим на примере такого теста:

def test_exception():
    try:
        function_with_exception(0)
    except Exception as e:
        assert e

Этот код пытается протестировать ситуацию, при которой функция function_with_exception() выбрасывает исключение, если ей передать 0. Как думаете, этот тест проверит, что функция действительно порождает исключение?

На самом деле, нет. Если функция function_with_exception() не выбросит исключение, то тест пройдет, потому что код не попадет в блок except.

В Pytest встроен контекстный менеджер, который самостоятельно отлавливает исключение и проверяет, что оно вообще было сгенерировано:

import pytest

def test_exception():
    with pytest.raises(Exception):
        function_with_exception(0)

Здесь raises перехватывает только те исключения, которые являются подтипами переданного класса. Благодаря этому мы можем управлять ожидаемым поведением и ловить только те ошибки, которые хотим поймать.

Более того, можно проверить конкретное сообщение, которое пришло вместе с исключением:

import pytest

def test_exception():
    # Добавляем: as e. Здесь e – произвольное имя переменной, содержащей исключение
    with pytest.raises(Exception) as e:
        function_with_exception(0)

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно


  • 130 курсов, 2000+ часов теории

  • 1000 практических заданий в браузере

  • 360 000 студентов

Наши выпускники работают в компаниях:

Рекомендуемые программы

профессия


от 6 300 ₽ в месяц

Разработка веб-приложений на Django

Понравилась статья? Поделить с друзьями:

Интересное по теме:

  • Отправка сообщила об ошибке 0x8004010f
  • Отправка отчета об ошибке planet zoo
  • Отрицательно повлиять лексическая ошибка
  • Отправка смс ошибка 28 теле2 что это
  • Отправить сообщение об ошибке автору

  • Добавить комментарий

    ;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: