Обратная функция ошибок python

Are there any pure-python implementations of the inverse error function?

I know that SciPy has scipy.special.erfinv(), but that relies on some C extensions. I’d like a pure python implementation.

I’ve tried writing my own using the Wikipedia and Wolfram references, but it always seems to diverge from the true value when the arg is > 0.9.

I’ve also attempted to port the underlying C code that Scipy uses (ndtri.c and the cephes polevl.c functions) but that’s also not passing my unit tests.


Edit: As requested, I’ve added the ported code.

Docstrings (and doctests) have been removed because they’re longer than the functions. I haven’t yet put much effort into making the port more pythonic — I’ll worry about that once I get something that passes unit tests.

Supporting functions from cephes polevl.c

def polevl(x, coefs, N):
    ans = 0
    power = len(coefs) - 1
    for coef in coefs[:N]:
        ans += coef * x**power
        power -= 1

    return ans

def p1evl(x, coefs, N):
    return polevl(x, [1] + coefs, N)

Main Inverse Error Function

def inv_erf(z):
    if z < -1 or z > 1:
        raise ValueError("`z` must be between -1 and 1 inclusive")

    if z == 0:
        return 0
    if z == 1:
        return math.inf
    if z == -1:
        return -math.inf

    # From scipy special/cephes/ndrti.c
    def ndtri(y):
        # approximation for 0 <= abs(z - 0.5) <= 3/8
        P0 = [
            -5.99633501014107895267E1,
            9.80010754185999661536E1,
            -5.66762857469070293439E1,
            1.39312609387279679503E1,
            -1.23916583867381258016E0,
        ]

        Q0 = [
            1.95448858338141759834E0,
            4.67627912898881538453E0,
            8.63602421390890590575E1,
            -2.25462687854119370527E2,
            2.00260212380060660359E2,
            -8.20372256168333339912E1,
            1.59056225126211695515E1,
            -1.18331621121330003142E0,
        ]

        # Approximation for interval z = sqrt(-2 log y ) between 2 and 8
        # i.e., y between exp(-2) = .135 and exp(-32) = 1.27e-14.
        P1 = [
            4.05544892305962419923E0,
            3.15251094599893866154E1,
            5.71628192246421288162E1,
            4.40805073893200834700E1,
            1.46849561928858024014E1,
            2.18663306850790267539E0,
            -1.40256079171354495875E-1,
            -3.50424626827848203418E-2,
            -8.57456785154685413611E-4,
        ]

        Q1 = [
            1.57799883256466749731E1,
            4.53907635128879210584E1,
            4.13172038254672030440E1,
            1.50425385692907503408E1,
            2.50464946208309415979E0,
            -1.42182922854787788574E-1,
            -3.80806407691578277194E-2,
            -9.33259480895457427372E-4,
        ]

        # Approximation for interval z = sqrt(-2 log y ) between 8 and 64
        # i.e., y between exp(-32) = 1.27e-14 and exp(-2048) = 3.67e-890.
        P2 = [
            3.23774891776946035970E0,
            6.91522889068984211695E0,
            3.93881025292474443415E0,
            1.33303460815807542389E0,
            2.01485389549179081538E-1,
            1.23716634817820021358E-2,
            3.01581553508235416007E-4,
            2.65806974686737550832E-6,
            6.23974539184983293730E-9,
        ]

        Q2 = [
            6.02427039364742014255E0,
            3.67983563856160859403E0,
            1.37702099489081330271E0,
            2.16236993594496635890E-1,
            1.34204006088543189037E-2,
            3.28014464682127739104E-4,
            2.89247864745380683936E-6,
            6.79019408009981274425E-9,
        ]

        s2pi = 2.50662827463100050242
        code = 1

        if y > (1.0 - 0.13533528323661269189):      # 0.135... = exp(-2)
            y = 1.0 - y
            code = 0

        if y > 0.13533528323661269189:
            y = y - 0.5
            y2 = y * y
            x = y + y * (y2 * polevl(y2, P0, 4) / p1evl(y2, Q0, 8))
            x = x * s2pi
            return x

        x = math.sqrt(-2.0 * math.log(y))
        x0 = x - math.log(x) / x

        z = 1.0 / x
        if x < 8.0:                 # y > exp(-32) = 1.2664165549e-14
            x1 = z * polevl(z, P1, 8) / p1evl(z, Q1, 8)
        else:
            x1 = z * polevl(z, P2, 8) / p1evl(z, Q2, 8)

        x = x0 - x1
        if code != 0:
            x = -x

        return x

    result = ndtri((z + 1) / 2.0) / math.sqrt(2)

    return result

Существуют ли какие-либо реализации pure-python функции обратной ошибки?

Я знаю, что SciPy имеет scipy.special.erfinv(), но это зависит от некоторых расширений C. Мне нужна чистая реализация python.

Я пробовал писать свои собственные, используя ссылки Википедии и Вольфрама, но он всегда кажется отличным от истинного значения, когда arg> 0.9.

Я также попытался перенести базовый код C, который использует Scipy ( ndtri.c и cephes polevl.c), но также не передает мои модульные тесты.


Изменение: по запросу я добавил портированный код.

Докстры (и доктрины) были удалены, потому что они длиннее функций. Я еще не приложил много усилий, чтобы сделать порт более pythonic — я буду беспокоиться об этом, как только я получу что-то, что проходит модульные тесты.

Вспомогательные функции от cephes polevl.c

def polevl(x, coefs, N):
    ans = 0
    power = len(coefs) - 1
    for coef in coefs[:N]:
        ans += coef * x**power
        power -= 1

    return ans

def p1evl(x, coefs, N):
    return polevl(x, [1] + coefs, N)

Основная функция инверсной ошибки

def inv_erf(z):
    if z < -1 or z > 1:
        raise ValueError("'z' must be between -1 and 1 inclusive")

    if z == 0:
        return 0
    if z == 1:
        return math.inf
    if z == -1:
        return -math.inf

    # From scipy special/cephes/ndrti.c
    def ndtri(y):
        # approximation for 0 <= abs(z - 0.5) <= 3/8
        P0 = [
            -5.99633501014107895267E1,
            9.80010754185999661536E1,
            -5.66762857469070293439E1,
            1.39312609387279679503E1,
            -1.23916583867381258016E0,
        ]

        Q0 = [
            1.95448858338141759834E0,
            4.67627912898881538453E0,
            8.63602421390890590575E1,
            -2.25462687854119370527E2,
            2.00260212380060660359E2,
            -8.20372256168333339912E1,
            1.59056225126211695515E1,
            -1.18331621121330003142E0,
        ]

        # Approximation for interval z = sqrt(-2 log y ) between 2 and 8
        # i.e., y between exp(-2) = .135 and exp(-32) = 1.27e-14.
        P1 = [
            4.05544892305962419923E0,
            3.15251094599893866154E1,
            5.71628192246421288162E1,
            4.40805073893200834700E1,
            1.46849561928858024014E1,
            2.18663306850790267539E0,
            -1.40256079171354495875E-1,
            -3.50424626827848203418E-2,
            -8.57456785154685413611E-4,
        ]

        Q1 = [
            1.57799883256466749731E1,
            4.53907635128879210584E1,
            4.13172038254672030440E1,
            1.50425385692907503408E1,
            2.50464946208309415979E0,
            -1.42182922854787788574E-1,
            -3.80806407691578277194E-2,
            -9.33259480895457427372E-4,
        ]

        # Approximation for interval z = sqrt(-2 log y ) between 8 and 64
        # i.e., y between exp(-32) = 1.27e-14 and exp(-2048) = 3.67e-890.
        P2 = [
            3.23774891776946035970E0,
            6.91522889068984211695E0,
            3.93881025292474443415E0,
            1.33303460815807542389E0,
            2.01485389549179081538E-1,
            1.23716634817820021358E-2,
            3.01581553508235416007E-4,
            2.65806974686737550832E-6,
            6.23974539184983293730E-9,
        ]

        Q2 = [
            6.02427039364742014255E0,
            3.67983563856160859403E0,
            1.37702099489081330271E0,
            2.16236993594496635890E-1,
            1.34204006088543189037E-2,
            3.28014464682127739104E-4,
            2.89247864745380683936E-6,
            6.79019408009981274425E-9,
        ]

        s2pi = 2.50662827463100050242
        code = 1

        if y > (1.0 - 0.13533528323661269189):      # 0.135... = exp(-2)
            y = 1.0 - y
            code = 0

        if y > 0.13533528323661269189:
            y = y - 0.5
            y2 = y * y
            x = y + y * (y2 * polevl(y2, P0, 4) / p1evl(y2, Q0, 8))
            x = x * s2pi
            return x

        x = math.sqrt(-2.0 * math.log(y))
        x0 = x - math.log(x) / x

        z = 1.0 / x
        if x < 8.0:                 # y > exp(-32) = 1.2664165549e-14
            x1 = z * polevl(z, P1, 8) / p1evl(z, Q1, 8)
        else:
            x1 = z * polevl(z, P2, 8) / p1evl(z, Q2, 8)

        x = x0 - x1
        if code != 0:
            x = -x

        return x

    result = ndtri((z + 1) / 2.0) / math.sqrt(2)

    return result

Содержание

  1. Функции Math.erf() и erfc() в Python с примерами
  2. Что такое функция erf() в Python?
  3. Синтаксис
  4. Параметры
  5. Возвращаемое значение
  6. Примеры программ по методу erf() в Python
  7. Пример 1. Программа, демонстрирующая работу метода erf()
  8. Пример 2. Программа для передачи значения вне допустимого диапазона из функции и отображения вывода
  9. Что такое функция erfc() в Python?
  10. Pure-Python обратная функция ошибок
  11. Вспомогательные функции от cephes polevl.c
  12. Главная обратная функция ошибок
  13. Pure-Python обратная функция ошибок
  14. Вспомогательные функции от cephes polevl.c
  15. Главная обратная функция ошибок

Функции Math.erf() и erfc() в Python с примерами

Метод Python math.erf() вернет ошибку в точке x. Функция ошибки также известна как функция ошибки Гаусса: она выдает ошибку, если в качестве параметра передается любое не числовое значение.

Python math.erfc() — это встроенный метод, определенный в математическом модуле, который используется для поиска дополнительной функции ошибок в точке x, если x — переданный параметр. Например, если x передается в качестве параметра в функцию erf(erf(x)), она возвращает дополнительную функцию ошибки.

Что такое функция erf() в Python?

Python erf() — это встроенный метод, определенный в математическом модуле, который используется для поиска функции ошибки в точке x, если x является переданным параметром. Например, если x передается в качестве параметра в функцию erf(erf(x)), она возвращает функцию ошибки. Мы можем использовать математический модуль, импортировав его.

Синтаксис

Здесь var — это переменная, функцию ошибки которой мы должны найти.

Параметры

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

Возвращаемое значение

Возвращает значение функции ошибки числа в типе данных float.

Примеры программ по методу erf() в Python

Функция Math.erf() в Python

Пример 1. Программа, демонстрирующая работу метода erf()

В этом примере кода мы видели, что, передавая параметр vaild, который отличается для разных примеров, мы получаем желаемое решение метода erf().

Пример 2. Программа для передачи значения вне допустимого диапазона из функции и отображения вывода

В этом примере мы видим, что при передаче параметра, который не имеет числового типа, функция выдает ошибку.
Python erfc() — это встроенный метод, определенный в математическом модуле, который используется для поиска дополнительной функции ошибок в точке x, если x — переданный параметр. Например, если x передается в качестве параметра в функцию erf(erf(x)), она возвращает дополнительную функцию ошибки.

Что такое функция erfc() в Python?

Функция Python math.erfc() возвращает дополнительную функцию ошибки в точке x. Функция ошибки также известна как функция ошибки Гаусса, и функция выдает ошибку, если в качестве параметра передается любое не числовое значение. Определяется как 1,0 – erf(x). Используется для большого значения x, если мы вычтем из 1, то это приведет к потере значимости.

Мы можем использовать математический модуль, импортировав его. После импорта мы используем для вызова этой функции статический объект.

Источник

Pure-Python обратная функция ошибок

Существуют ли чисто Python реализации функции обратной ошибки?

Я знаю, что SciPy имеет scipy.special.erfinv (), но это зависит от некоторых расширений Си. Я хотел бы чистую реализацию Python.

Я пытался написать свои собственные, используя ссылки на Википедию и Вольфрама, но всегда кажется, что они отличаются от истинного значения, когда аргумент> 0,9.

Я также попытался портировать основной код C, который использует Scipy ( ndtri.c и cephes polevl.c функции), но это также не проходит мои юнит-тесты.

Изменить: По запросу я добавил портированный код.

Строки документации (и тесты документов) были удалены, потому что они длиннее функций. Я еще не приложил много усилий, чтобы сделать порт более питоническим — я буду беспокоиться об этом, как только получу что-то, что проходит модульные тесты.

Вспомогательные функции от cephes polevl.c

def polevl(x, coefs, N): ans = 0 power = len(coefs) - 1 for coef in coefs[:N]: ans += coef * x**power power -= 1 return ans def p1evl(x, coefs, N): return polevl(x, [1] + coefs, N) 

Главная обратная функция ошибок

def inv_erf(z): if z < -1 or z >1: raise ValueError("`z` must be between -1 and 1 inclusive") if z == 0: return 0 if z == 1: return math.inf if z == -1: return -math.inf # From scipy special/cephes/ndrti.c def ndtri(y): # approximation for 0 (1.0 - 0.13533528323661269189): # 0.135. = exp(-2) y = 1.0 - y code = 0 if y > 0.13533528323661269189: y = y - 0.5 y2 = y * y x = y + y * (y2 * polevl(y2, P0, 4) / p1evl(y2, Q0, 8)) x = x * s2pi return x x = math.sqrt(-2.0 * math.log(y)) x0 = x - math.log(x) / x z = 1.0 / x if x < 8.0: # y >exp(-32) = 1.2664165549e-14 x1 = z * polevl(z, P1, 8) / p1evl(z, Q1, 8) else: x1 = z * polevl(z, P2, 8) / p1evl(z, Q2, 8) x = x0 - x1 if code != 0: x = -x return x result = ndtri((z + 1) / 2.0) / math.sqrt(2) return result 

Источник

Pure-Python обратная функция ошибок

Существуют ли pure-python реализации функции обратной ошибки?

Я знаю, что у SciPy есть scipy.special .erfinv (), но это зависит от некоторых расширений Си. Я хотел бы чистую реализацию Python.

Я пробовал писать свои собственные, используя Википедию и Wolfram ссылается, но всегда кажется, что оно отличается от истинного значения, когда arg> 0,9.

Я также попытался портировать основной код C, который использует Сципи ( ndtri.c и cephes < > функции), но это также не проходит мои юнит-тесты.

Изменить: По запросу я добавил портированный код.

Строки документации (и тесты документов) были удалены, потому что они длиннее функций. Я еще не приложил много усилий, чтобы сделать порт более питоническим — я буду беспокоиться об этом, как только получу что-то, что проходит модульные тесты.

Вспомогательные функции от cephes polevl.c

def polevl(x, coefs, N): ans = 0 power = len(coefs) - 1 for coef in coefs[:N]: ans += coef * x**power power -= 1 return ans def p1evl(x, coefs, N): return polevl(x, [1] + coefs, N) 

Главная обратная функция ошибок

def inv_erf(z): if z < -1 or z >1: raise ValueError("`z` must be between -1 and 1 inclusive") if z == 0: return 0 if z == 1: return math.inf if z == -1: return -math.inf # From scipy special/cephes/ndrti.c def ndtri(y): # approximation for 0 (1.0 - 0.13533528323661269189): # 0.135. = exp(-2) y = 1.0 - y code = 0 if y > 0.13533528323661269189: y = y - 0.5 y2 = y * y x = y + y * (y2 * polevl(y2, P0, 4) / p1evl(y2, Q0, 8)) x = x * s2pi return x x = math.sqrt(-2.0 * math.log(y)) x0 = x - math.log(x) / x z = 1.0 / x if x < 8.0: # y >exp(-32) = 1.2664165549e-14 x1 = z * polevl(z, P1, 8) / p1evl(z, Q1, 8) else: x1 = z * polevl(z, P2, 8) / p1evl(z, Q2, 8) x = x0 - x1 if code != 0: x = -x return x result = ndtri((z + 1) / 2.0) / math.sqrt(2) return result 

Источник

The issue

The Phantom Menace

Say i wrote a function decorator which takes the function, and wraps it in another function like so:

# File example-1.py
from functools import wraps

def decorator(func):
    # Do something
    @wraps(func)
    def wrapper(*args, **kwargs):
        # Do something
        return func(*args, **kwargs)
        # Do something
    # Do something
    return wrapper

Now lets suppose the function I’m decorating raises an exception:

@decorator
def foo():
    raise Exception('test')

The result of running foo() will print out the following traceback (In any Python version):

Traceback (most recent call last):
  File "./example-1.py", line 20, in <module>
    foo()
  File "./example-1.py", line 11, in wrapper
    return func(*args, **kwargs)
  File "./example-1.py", line 18, in foo
    raise Exception('test')
Exception: test

Attack of the Clones

OK, now i look at my traceback and i see it goes through the wrapper function. What if I wrapped the function multiple times(presumably with a slightly more sophisticated decorator object which receives arguments in its constructor)? What if I use this decorator often in my code(I use it for logging, or profiling, or whatever)?

Traceback (most recent call last):
  File "./example-1.py", line 20, in <module>
    foo()
  File "./example-1.py", line 11, in wrapper
    return func(*args, **kwargs)
  File "./example-1.py", line 11, in wrapper
    return func(*args, **kwargs)
  File "./example-1.py", line 11, in wrapper
    return func(*args, **kwargs)
  File "./example-1.py", line 11, in wrapper
    return func(*args, **kwargs)
  File "./example-1.py", line 18, in foo
    raise Exception('test')
Exception: test

I don’t want it «polluting» my traceback when i know from the function definition that the wrapper is there, and i don’t want it showing up multiple times when the code snippet it displays is the unhelpful return func(*args, **kwargs)

Python 2

Revenge of the Sith

In Python-2, as this answer to a different question points out, the following trick does the job:

# In file example-2.py

def decorator(func):
    # Do something
    @wraps(func)
    def wrapper(*args, **kwargs):
        # Do something
        info = None
        try:
            return func(*args, **kwargs)
        except:
            info = sys.exc_info()
            raise info[0], info[1], info[2].tb_next
        finally:
            # Break the cyclical reference created by the traceback object
            del info
        # Do something
    # Do something
    return wrapper

By directly wrapping the call to the wrapped function with this idiom in the same block as the function I want to elide from the traceback, I effectively remove the current layer from the traceback and let the exception keep propagating. Every time the stack unwinding goes through this function, it removes itself from the traceback so this solution works perfectly:

Traceback (most recent call last):
  File "./example-2.py", line 28, in <module>
    foo()
  File "./example-2.py", line 26, in foo
    raise Exception('test')
Exception: test

(Note however that you can not encapsulate this idiom in another function, since as soon the stack will unwind from that function back into wrapper, it will still be added to the traceback)

Python 3

A New Hope

Now that we have this covered, lets move along to Python-3. Python-3 introduced this new syntax:

raise_stmt ::=  "raise" [expression ["from" expression]]

which allows chaining exceptions using the __cause__ attribute of the new exception. This feature is uninteresting to us, since it modifies the exception, not the traceback. Our goal is to be a completely transparent wrapper, as far as visibility goes, so this won’t do.

Alternatively, we can try the following syntax, which promises to do what we want (code example taken from the python documentation):

raise Exception("foo occurred").with_traceback(tracebackobj)

Using this syntax we may try something like this:

# In file example-3
def decorator(func):
    # Do something
    @wraps(func)
    def wrapper(*args, **kwargs):
        # Do something
        info = None
        try:
            return func(*args, **kwargs)
        except:
            info = sys.exc_info()
            raise info[1].with_traceback(info[2].tb_next)
        finally:
            # Break the cyclical reference created by the traceback object
            del info
        # Do something
    # Do something
    return wrapper

The Empire Strikes Back

But, unfortunately, this does not do what we want:

Traceback (most recent call last):
  File "./example-3.py", line 29, in <module>
    foo()
  File "./example-3.py", line 17, in wrapper
    raise info[1].with_traceback(info[2].tb_next)
  File "./example-3.py", line 27, in foo
    raise Exception('test')
Exception: test

As you can see, the line executing the raise statement shows up in the traceback. This seems to come from the fact that while the Python-2 syntax sets the traceback from the third argument to raise as the function is being unwound, and thus it is not added to the traceback chain(as explained in the docs under Data Model), the Python-3 syntax on the other hand changes the traceback on the Exception object as an expression inside the functions context, and then passes it to the raise statement which adds the new location in code to the traceback chain (the explanation of this is very similar in Python-3).

A workaround that comes to mind is avoiding the "raise" [ expression ] form of the statement, and instead use the clean raise statement to let the exception propagate as usual but modify the exception objects __traceback__ attribute manually:

# File example-4
def decorator(func):
    # Do something
    @wraps(func)
    def wrapper(*args, **kwargs):
        # Do something
        info = None
        try:
            return func(*args, **kwargs)
        except:
            info = sys.exc_info()
            info[1].__traceback__ = info[2].tb_next
            raise
        finally:
            # Break the cyclical reference created by the traceback object
            del info
        # Do something
    # Do something
    return wrapper

But this doesn’t work at all!

Traceback (most recent call last):
  File "./example-4.py", line 30, in <module>
    foo()
  File "./example-4.py", line 14, in wrapper
    return func(*args, **kwargs)
  File "./example-4.py", line 28, in foo
    raise Exception('test')
Exception: test

Return of the Jedi(?)

So, what else can i do? It seems like using the «traditional» way of doing this just won’t work because of the change in syntax, and I wouldn’t want to start messing with the traceback printing mechanism (using the traceback module) at the project level. This is because it’ll be hard if not impossible to implement in an extensible which won’t be disruptive to any other package that tries to change the traceback, print the traceback in a custom format at the top level, or otherwise do anything else related to the issue.

Also, can someone explain why in fact the last technique fails completely?

(I tried these examples on python 2.6, 2.7, 3.4, 3.6)

EDIT: After thinking about it for a while, in my opinion the python 3 behavior makes more sense, to the point that the python 2 behavior almost looks like a design bug, but I still think that there should be a way to do this kinda stuff.

Уровень сложности
Средний

Время на прочтение
8 мин

Количество просмотров 14K

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

  • Что такое обработка исключений?

  • Разница между оператором if и обработкой исключений.

  • Использование разделов else и finally блока try-except для организации правильного обращения с ошибками.

  • Определение пользовательских исключений.

  • Рекомендации по обработке исключений.

Что такое обработка исключений?

Обработка исключений — это процесс написания кода для перехвата и обработки ошибок или исключений, которые могут возникать при выполнении программы. Это позволяет разработчикам создавать надёжные программы, которые продолжают работать даже при возникновении неожиданных событий или ошибок. Без системы обработки исключений подобное обычно приводит к фатальным сбоям.

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

Различия между оператором if и обработкой исключений

Главные различия между оператором if и обработкой исключений в Python произрастают из их целей и сценариев использования.

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

temperature = int(input("Please enter temperature in Fahrenheit: "))
if temperature > 100:
    print("Hot weather alert! Temperature exceeded 100°F.")
elif temperature >= 70:
    print("Warm day ahead, enjoy sunny skies.")
else:
    print("Bundle up for chilly temperatures.")

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

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

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

# Определение функции, которая пытается поделить число на ноль
def divide(x, y):
    result = x / y
    return result
# Вызов функции divide с передачей ей x=5 и y=0
result = divide(5, 0)
print(f"Result of dividing {x} by {y}: {result}")

Вывод:

Traceback (most recent call last):
  File "<stdin>", line 8, in <module>
ZeroDivisionError: division by zero attempted

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

Вышеописанное исключение можно обработать, обернув вызов функции divide в блок try-except:

# Определение функции, которая пытается поделить число на ноль
def divide(x, y):
    result = x / y
    return result
# Вызов функции divide с передачей ей x=5 и y=0
try:
    result = divide(5, 0)
    print(f"Result of dividing {x} by {y}: {result}")
except ZeroDivisionError:
    print("Cannot divide by zero.")

Вывод:

Cannot divide by zero.

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

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

Использование разделов else и finally блока try-except для организации правильного обращения с ошибками

При работе с исключениями в Python рекомендуется включать в состав блоков try-except и раздел else, и раздел finally. Раздел else позволяет программисту настроить действия, производимые в том случае, если при выполнении кода, который защищают от проблем, не было вызвано исключений. А раздел finally позволяет обеспечить обязательное выполнение неких заключительных операций, вроде освобождения ресурсов, независимо от факта возникновения исключений (вот и вот — полезные материалы об этом).

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

Использование разделов else и finally позволяет поступить именно так — обработать данные обычным образом в том случае, если исключений не возникло, либо обработать любые исключения, но, как бы ни развивались события, в итоге закрыть файл. Без этих разделов код страдал бы уязвимостями в виде утечки ресурсов или неполной обработки ошибок. В результате оказывается, что else и finally играют важнейшую роль в создании устойчивых к ошибкам и надёжных программ.

try:
    # Открытие файла в режиме чтения
    file = open("file.txt", "r")
    print("Successful opened the file")
except FileNotFoundError:
    # Обработка ошибки, возникающей в том случае, если файл не найден
    print("File Not Found Error: No such file or directory")
    exit()
except PermissionError:
    # Обработка ошибок, связанных с разрешением на доступ к файлу
    print("Permission Denied Error: Access is denied")
else:
    # Всё хорошо - сделать что-то с данными, прочитанными из файла
    content = file.read().decode('utf-8')
    processed_data = process_content(content)
    
# Прибираемся после себя даже в том случае, если выше возникло исключение
finally:
    file.close()

В этом примере мы сначала пытаемся открыть файл file.txt для чтения (в подобной ситуации можно использовать выражение with, которое гарантирует правильное автоматическое закрытие объекта файла после завершения работы). Если в процессе выполнения операций файлового ввода/вывода возникают ошибки FileNotFoundError или PermissionError — выполняются соответствующие разделы except. Здесь, ради простоты, мы лишь выводим на экран сообщения об ошибках и выходим из программы в том случае, если файл не найден.

В противном случае, если в блоке try исключений не возникло, мы продолжаем работу, обрабатывая содержимое файла в ветви else. И наконец — выполняется «уборка» — файл закрывается независимо от возникновения исключения. Это обеспечивает блок finally (подробности смотрите здесь).

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

Определение пользовательских исключений

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

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

Вот пример определения пользовательского исключения, названного InvalidEmailAddress:

class InvalidEmailAddress(ValueError):
    def __init__(self, message):
        super().__init__(message)
        self.msgfmt = message

Это исключение является наследником ValueError. Его конструктор принимает необязательный аргумент message (по умолчанию он устанавливается в значение invalid email address).

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

def send_email(address):
    if isinstance(address, str) == False:
        raise InvalidEmailAddress("Invalid email address")
# Отправка электронного письма

Теперь, если функции send_email() будет передана строка, содержащая неправильно оформленный адрес, то, вместо сообщения стандартной ошибки TypeError, будет выдано настроенное заранее сообщение об ошибке, которое чётко указывает на возникшую проблему. Например, это может выглядеть так:

>>> send_email(None)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/path/to/project/main.py", line 8, in send_email
    raise InvalidEmailAddress("Invalid email address")
InvalidEmailAddress: Invalid email address

Рекомендации по обработке исключений

Вот несколько рекомендаций, относящихся к обработке ошибок в Python:

  1. Проектируйте код в расчёте на возможное возникновение ошибок. Заранее планируйте устройство кода с учётом возможных сбоев и проектируйте программы так, чтобы они могли бы достойно обрабатывать эти сбои. Это означает — предугадывать возможные пограничные случаи и реализовывать подходящие обработчики ошибок.

  2. Используйте содержательные сообщения об ошибках. Сделайте так, чтобы программа выводила бы, на экран, или в файл журнала, подробные сообщения об ошибках, которые помогут пользователям понять — что и почему пошло не так. Старайтесь не применять обобщённые сообщения об ошибках, наподобие Error occurred или Something bad happened. Вместо этого подумайте об удобстве пользователя и покажите сообщение, в котором будет дан совет по решению проблемы или будет приведена ссылка на документацию. Постарайтесь соблюсти баланс между выводом подробных сообщений и перегрузкой пользовательского интерфейса избыточными данными.

  3. Минимизируйте побочные эффекты. Постарайтесь свести к минимуму последствия сбойных операций, изолируя проблемные разделы кода посредством конструкции try-finally или try с использованием with. Сделайте так, чтобы после выполнения кода, было ли оно удачным или нет, обязательно выполнялись бы «очистительные» операции.

  4. Тщательно тестируйте код. Обеспечьте корректное поведение обработчиков ошибок в различных сценариях использования программы, подвергнув код всеобъемлющему тестированию.

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

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

Итоги

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

О, а приходите к нам работать? 🤗 💰

Мы в wunderfund.io занимаемся высокочастотной алготорговлей с 2014 года. Высокочастотная торговля — это непрерывное соревнование лучших программистов и математиков всего мира. Присоединившись к нам, вы станете частью этой увлекательной схватки.

Мы предлагаем интересные и сложные задачи по анализу данных и low latency разработке для увлеченных исследователей и программистов. Гибкий график и никакой бюрократии, решения быстро принимаются и воплощаются в жизнь.

Сейчас мы ищем плюсовиков, питонистов, дата-инженеров и мл-рисерчеров.

Присоединяйтесь к нашей команде.

Понравилась статья? Поделить с друзьями:
  • Обратный анализ ошибок
  • Обратная планка ошибки
  • Обозначение ошибок рено премиум dxi
  • Обратные отжимания ошибки
  • Обратите внимание на постскриптум речевая ошибка