Каковы альтернативы кодам состояния, к которым можно получить доступ из места вызова функции? Исключения? (Питон, Джанго)

Я хотел бы вызвать исключение в функции, а затем проверить где-то еще (в представлении Django и моих модульных тестах), было ли оно вызвано. В следующем коде используются коды состояния, и он работает. Но я не могу понять, как сделать то же самое с исключениями, которые, по общему мнению, являются правильным способом делать такие вещи.

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

Я понятия не имею, как я буду проверять add_foo_view, если в utils.add_foo возникнет исключение.

В модульном тесте я пробовал такие вещи, как assertWarnsRegex(Warning, 'blah went wrong'), но не удосужился проверить, действительно ли сообщение такое же.

views.py:

from django.contrib import messages

from .utils import add_foo


def add_foo_view(request):
    if request.method == 'POST':

        status = add_foo(request.POST['bar'])
        if not status == 'Bar was added.':
            messages.error(request, status)

        return render(request, 'index.html')
    else:
        return render(request, 'add_foo.html')

utils.py:

def add_foo(bar):

    if not spamifyable(bar):
        return 'Bar can not be spamified.'

    try:
        eggs = Egg.objects.get(baz=bar)
    except:
        return 'Bar has no eggs.'

    do_things(bar)

    return 'Bar was added.'

tests.py:

def test_bar_without_eggs(self):

    status = add_foo(eggless_bar)

    assertEqual(status, 'Bar has no eggs.')

Я использую Python 3.5.2 и Django 1.11.4.

Изменить: я не совсем уверен, что исключения будут здесь правильным выбором. Я часто читал, что исключения бывают только для неожиданных вещей. Но случаи, которые я улавливаю здесь, — это неверные данные пользователя, которые очень ожидаемы. Поэтому мой вопрос на самом деле не в том, как сделать это с исключениями, а в том, как сделать это правильным и питоническим способом. В любом случае я хочу, чтобы проверка происходила в отдельном utils месте (обычный Python, не Джанго), а не в представлении.


person Watchduck    schedule 29.08.2017    source источник


Ответы (2)


Вы можете использовать оператор 'raise' для создания исключения, например:

raise Exception("Bar has no eggs.")

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

class MyException(Exception):
    pass

тогда вы можете сделать:

raise MyException("Bar has no eggs.")

И вы можете поймать возникшие исключения, используя блок try-except:

try:
    function_that_raises_exception(args)
except MyException:
    function_to _handle_exception(args)

Итак, в вашем views.py вы можете сделать:

from django.contrib import messages

from .utils import add_foo, MyException


def add_foo_view(request):
    if request.method == 'POST':
        try:
            add_foo(request.POST['bar'])
        except MyException as e:
            messages.error(request, str(e))

        return render(request, 'index.html')
    else:
        return render(request, 'add_foo.html')
person Siddardha    schedule 29.08.2017
comment
Но вы по-прежнему используете status, что по сути является кодом ошибки. Как узнать в представлении, является ли проблема невозможной спамить или не имеет яиц, используя только исключения? - person Watchduck; 30.08.2017
comment
Вы можете заменить return 'Bar has no eggs.' на raise MyException("Bar has no eggs."), тогда вам не нужно проверять наличие status. - person Siddardha; 30.08.2017
comment
Я понял. Но как мне проверить в представлении (или в тестах), в чем была реальная проблема — MyException("Bar can not be spamified.") или MyException("Bar has no eggs.")? Я хочу проверить сообщение об ошибке. И я не думаю, что было бы разумно создавать столько подклассов, сколько у меня сообщений об ошибках (UnpamifyableException, NoEggsException). - person Watchduck; 30.08.2017
comment
Я отредактирую свой ответ в соответствии с моим пониманием вашего вопроса. Я предполагаю, что вы должны сделать messages.error(request, status), если это исключение поймано. - person Siddardha; 30.08.2017
comment
Да. Поэтому, если решение включает что-то вроде except MyException as e: в представлении (что не сработало для меня), ниже будет что-то вроде messages.error(request, e.message). - person Watchduck; 30.08.2017
comment
если вы сделаете except MyException as e:, вы можете использовать str(e) в своем блоке исключений, чтобы получить сообщение об ошибке. Надеюсь, я правильно понял ваш вопрос. - person Siddardha; 30.08.2017
comment
Возможно, вы забыли импортировать MyException из utils. Используйте: from .utils import add_foo, MyException в вашем views.py - person Siddardha; 30.08.2017
comment
Кроме того, я думаю, что то, что вы делали с return 'Bar has no eggs.', было неплохим выбором. Однако я бы назвал это как-то иначе, чем «код состояния». - person Siddardha; 30.08.2017
comment
Это работает, и я думаю, что это легко читать, так что это не может быть ужасным выбором. Но я думаю, что должен быть более общепринятый способ сделать это в Python. Вся эта штука except MyException as e у меня не работает, потому что я получаю следующую ошибку: catching classes that do not inherit from BaseException is not allowed - person Watchduck; 30.08.2017
comment
Нашел это уже. Также происходит, когда я делаю except Exception('blah'). Аргумент — это проблема. Просто except Exception as e работает. - person Watchduck; 30.08.2017

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

Я могу создать собственное исключение и добавить свойство message в файл __init__. Таким образом, к нему можно получить доступ как e.message, который в моем случае будет ретранслирован на индексную страницу в views.py.

utils.py

class AddFooException(Exception):

    def __init__(self, message):
        self.message = message


def add_foo(bar):

    if not spamifyable(bar):
        raise AddFooException('Bar can not be spamified.')

    try:
        eggs = Egg.objects.get(baz=bar)
    except:
        raise AddFooException('Bar has no eggs.')

    do_things(bar)

views.py

from django.contrib import messages

from .utils import add_foo, AddFooException


def add_foo_view(request):
    if request.method == 'POST':
        bar = request.POST['bar']

        try:
            add_foo(bar)
        except AddFooException as e:
            messages.error(request, e.message)


        return render(request, 'index.html')
    else:
        return render(request, 'add_foo.html')
person Watchduck    schedule 31.05.2018