2

Instead of calling the exception directly, I have seen it is subclassed with nothing in it or a pass statement. How does it help Python internally to subclass a base class, in this manner? Does it change namespace or signature? How?

class ACustomException(Exception):
    pass

class BCustomException(Exception):
    pass
halfer
  • 19,824
  • 17
  • 99
  • 186
nish
  • 1,008
  • 4
  • 17
  • 34
  • 1
    It doesn't help *python* it helps your code, by providing you with a specialized exception – juanpa.arrivillaga Feb 27 '20 at 02:40
  • Say you write a library Nish. When I am using your library I can try/except on NishError and I'm done. I don't need to have a million different except cases. – Gillespie Feb 27 '20 at 02:41
  • @Gillespie, could you give an example. Thank you – nish Feb 27 '20 at 02:44
  • Related: [Manually raising (throwing) an exception in Python](https://stackoverflow.com/q/2052390/4518341). [Aaron Hall's awesome answer](https://stackoverflow.com/a/24065533/4518341) discusses custom exceptions briefly at the bottom. – wjandrea Feb 27 '20 at 15:27

3 Answers3

5

Raising Exception is like telling the doctor "Something's wrong" and then refusing to answer any questions. Compare:

try:
    with open("foo.json", "rt") as r:
        new_count = json.load(r)["count"] + 1
except Exception:
    # Is the file missing?
    # Is the file there, but not readable?
    # Is the file readable, but does not contain valid JSON?
    # Is the file format okay, but the data's not a dict with `count`?
    # Is the entry `count` there, but is not a number?
    print("Something's wrong")
    # I don't care. You figure it out.

and

try:
    with open("data.json", "rt") as r:
        new_count = json.load(r)["count"] + 1
except FileNotFoundError:
    print("File is missing.")
except PermissionError:
    print("File not readable.")
except json.decoder.JSONDecoderError:
    print("File is not valid JSON.")
except KeyError:
    print("Cannot find count.")
except TypeError:
    print("Count is not a number.")

If you are making a library, you can use the predefined exception classes where appropriate — but sometimes you need to communicate errors that Python creators never thought about, or you need to make a finer distinction than the existing exceptions do. This is when you'd create a custom exception.

For example, Django will define django.contrib.auth.models.User.DoesNotExist exception to communicate that the code tried to look for a User in the database, but no User matching the given criteria could be found. Being able to catch django.contrib.auth.models.User.DoesNotExist is like being a doctor, and getting a patient that not only tells you what hurts, but brings X-rays and a printed family history with them.

Amadan
  • 191,408
  • 23
  • 240
  • 301
3

When you're handling exceptions with try-except, you're catching them by name, so having specific names helps you handle them.

For example, if a function raises Exception for any error, the catching logic gets complicated:

def foobar():
    if FOO:
        raise Exception('FOO happened')
    elif BAR:
        raise Exception('BAR happened')

try:
    foobar()
except Exception as e:
    if e.args == ('FOO happened',):
        print('Handling FOO')
    elif e.args == ('BAR happened',):
        print('Handling BAR')
    else:
        raise

On the other hand if you have subclassed exceptions, the catching logic is simple:

class FooError(Exception):
    pass

class BarError(Exception):
    pass

def function():
    if FOO:
        raise FooError('FOO happened')
    elif BAR:
        raise BarError('BAR happened')

try:
    function()
except FooError:
    print('Handling FOO')
except BarError:
    print('Handling BAR')
wjandrea
  • 28,235
  • 9
  • 60
  • 81
0

It helps with determining 'what' the traceback issue is referring to in case of maybe a web service that you maybe running, so it's not low-level or the generic errors you normally get back rather the exception class that would be used.

To be more specific with an example:

val = int(input('Enter a number:'))
try:
  val *= val
except ValueError as e:
  raise e
print(val)

### ValueError will be raised if user inputs something other than a number
### this raise e will return the actual error message saying 
### ValueError: invalid literal for int() with base 10: 'ok'

In your case, you could still raise your exception keeping ValueError as the except to be handled for, like this:

val = int(input('Enter a number:'))
try:
  val *= val
except ValueError as e:
  raise ACustomException('some debug statement referring to the cause of the error')

print(val)

### now this will raise your exception class besides the ValueError exception, with a debug statement if you choose to have one in it.
de_classified
  • 1,927
  • 1
  • 15
  • 19