Лучшая практика структурирования исключений модуля в Python3

Предположим, у меня есть проект с такой структурой папок.

/project
    __init__.py
    main.py
    /__helpers
        __init__.py
        helpers.py
        ...

Модуль helpers.py определяет некоторое исключение и содержит некоторый метод, вызывающий это исключение.

# /project/__helpers/helpers.py

class HelperException(Exception):
    pass

def some_function_that_raises():
    raise HelperException

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

# /projects/main.py

from project.__helpers.helpers import some_function_that_raises

class MainException(Exception):
    pass

Теперь я не хочу, чтобы пользователям приходилось делать from project.__helpers.helpers import HelperException, если они хотят поймать это исключение. Было бы разумнее иметь возможность импортировать исключение из общедоступного модуля, который его вызывает.

Но я не могу просто переместить HelperException в main.py, что привело бы к циклическому импорту.

Каков наилучший способ разрешить пользователям импортировать все исключения из main.py, пока те возникают в /__helpers?


person Olivier Melançon    schedule 04.01.2018    source источник


Ответы (1)


Вот решение, которое я придумал.

Идея состоит в том, чтобы поместить все исключения в один файл, из которого их можно импортировать, а затем импортировать их все в main.py. Чтобы сделать все чистым и явным, мы, наконец, определяем атрибут __all__ модуля.

Вот новая файловая структура

/project
    __init__.py
    main.py
    /__helpers
        __init__.py
        exceptions.py
        helpers.py
        ...

Вот файл exceptions.py.

# /project/__helpers/exceptions.py

class MainException(Exception):
    pass

# Note that this also allows us to inherit between exceptions
class HelperException(MainException):
    pass

Затем мы можем импортировать исключения из этого файла без риска циклической зависимости.

И, наконец, мы определяем __all__ в main.py, чтобы явно указать, что исключения должны быть импортированы.

# /projects/main.py

from project.__helpers.helpers import some_function_that_raises
from project.__helpers.exceptions import MainException, HelperException

__all__ = ['MainException', 'HelperException', ...]

Просто напомню, что атрибут __all__ определяет, что будет импортировано, если нужно сделать from project import *. Таким образом, это расширяет желаемое поведение до звезды импорта и делает явным, что мы хотим, чтобы исключения быть импортированы из этого файла.

Также обратите внимание, что некоторые IDE даже будут рассматривать 'HelperException' в __all__ как ссылку на HelperException и не будут беспокоить вас из-за неиспользуемого импорта. Это то, что заставляет меня думать, что это правильный подход.

person Olivier Melançon    schedule 05.01.2018