11

Since too many python operations return ValueError, how can we differentiate between them?

Example: I expect an iterable to have a single element, and I want to get it

  • a, = [1, 2]: ValueError: too many values to unpack
  • a, = []: ValueError: too few values to unpack

How can I differentiate between those two cases?? eg

try:
    a, = lst
except ValueError as e:
    if e.too_many_values:
        do_this()
    else:
        do_that()

I realise that in this particular case I could find a work-around using length/indexing, but the point is similar cases come up often, and I want to know if there's a general approach. I also realise I could check the error message for if 'too few' in message but it seems a bit crude.

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
blue_note
  • 27,712
  • 9
  • 72
  • 90
  • There's no normal answer, only "exotic". – Olvin Roght Sep 02 '19 at 13:25
  • 2
    In this example, it really looks like you want to avoid exceptions all together and makes a decision based on `len(lst)`. Unfortunately, there is nothing built-in `ValueError` that you can use to make the kind of decision you're looking for. – John Szakmeister Sep 02 '19 at 13:32
  • What stops you from checking `if not lst:` -> list is empty so too few? – Giacomo Alzetta Sep 02 '19 at 13:34
  • 2
    @GiacomoAlzetta The last paragraph of the question seems to answer your question. OP was asking a general question which was *illustrated* by a particular type of example. – John Coleman Sep 02 '19 at 13:35
  • A problem is that there are countless ways in which a piece of code can fail to provide a value when one is expected. `ValueError` is a catch-all for all such errors. I don't think that this general error could be programmed so that it have methods which correspond to all the ways such an error is raised. – John Coleman Sep 02 '19 at 13:41

2 Answers2

1
try:
    raise ValueError('my error')
except ValueError as e:

    # use str(), not repr(), see
    # https://stackoverflow.com/a/45532289/7919597

    x = getattr(e, 'message', str(e))

    if 'my error' in x:

        print('got my error')

(see also How to get exception message in Python properly)

But this might not be a clean solution after all.

The best thing would be to narrow the scope of your try block so that only one was possible. Or don't depend on exceptions to detect those error cases.

Joe
  • 6,758
  • 2
  • 26
  • 47
  • Is `getattr(e, 'message', ...)` really needed? Why not just use `str(e)`? – wjandrea Sep 02 '19 at 14:41
  • 1
    I have not figured out when exceptions do have the message attribute, but true, `str(e)` works for sure. – Joe Sep 02 '19 at 17:15
0

This isn't really an answer, because it only applies if you have some control over how the exceptions are raised. Since exceptions are just objects, you can just tack on other objects / flags to them. Not saying that this is a great thing to do or a great way of doing it:

from enum import Enum

class ValueErrorType(Enum):
    HelloType = 0,
    FooType = 1


def some_func(string):
    if "Hello" in string:
        error = ValueError("\"Hello\" is not allowed in my strings!!!!")
        error.error_type = ValueErrorType.HelloType
        raise error
    elif "Foo" in string:
        error = ValueError("\"Foo\" is also not allowed!!!!!!")
        error.error_type = ValueErrorType.FooType
        raise error

try:
    some_func("Hello World!")
except ValueError as error:
    error_type_map = {
        ValueErrorType.HelloType: lambda: print("It was a HelloType"),
        ValueErrorType.FooType: lambda: print("It was a FooType")
    }
    error_type_map[error.error_type]()

I'd be curious to know if there is some way you can achieve this with exceptions where you have no control over how they're raised.

Paul M.
  • 10,481
  • 2
  • 9
  • 15
  • thanks for answer. I do that when I create my own exceptions, but `ValueError` in particular appears everywhere, and you have no control on it. – blue_note Sep 02 '19 at 13:31