Skip to content

Latest commit

 

History

History
103 lines (81 loc) · 4.22 KB

custom-exceptions.md

File metadata and controls

103 lines (81 loc) · 4.22 KB

Custom exceptions

Exceptions are a way to deal with errors in your code.

For example imagine that you have a function that saves some data to the disk. And when the program executes, the disk happens to be full. In that case, an exception is "raised" (or "thrown"). Imagine it like an alternative result-value from your function.

In areas where the function is called, you can "catch" those exceptions and do something sensible. For example, in the case of a disk full, show an error to the user that says: "Error: Disk is full" or something similar.

Any exception that is not caught will cause the program to crash and exit.

So clearly, handling (catching) them and doing something other than crashing is nicer for the end-user.

Python comes with some standard/builtin exceptions. Each exception has a well defined reason why it might be thrown.

If those "builtin" exceptions are not appropriate for your own program, you can define your own. This is done by creating a new class inheriting from "Exception".

For example, let's say your program that creates files on disk only gives a user the permission to store 1GB of data. Everything above "exceeds the quota". The disk is still not full, so that error is not appropriate. So we need something else.

In that case we can create such a subclass:

class QuotaExceeded(Exception):
    pass

And then use it:

def store_file(user, file):
    quota = get_quota(user)
    used_diskspace = get_used_diskspace(user)
    filesieze = get_filesize(file)
    if (used_diskspace + filesize) > quota:
        raise QuotaExceeed(
            f"The file {file} would exceed the disk quota for {user}. "
            f"Currently {used_diskspace} out of {quota} is used. "
            f"Filesize: {filesize}"
        )
    store_file_to_disk(user, file)

The raise line in the if-block will immediately stop executing that function and "throw" that custom QuotaExceeded error. It can then be caught using the try/except block:

try:
    store_file(john_doe, uploaded_file)
except QuotaExceeded as exc:
    print(exc)

If that try/except block would not exist, the program would crash.

Sometimes you want your program to crash out instead of being in an "unknown/unpredictable" state. That's when exceptions come in handy.

To add to the example above, you can make custom exceptions more useful by adding a docstring and by storing useful context-values in the exception itself. For example:

class QuotaExceeded(Exception):
    """
    This exception is raised if an operation would cause more disk-space
    to be used than is allowed for a given user.
    """
    def __init__(self, file, user, used_diskspace, quota, filesize):
        super().__init__(
            f"The file {file} would exceed the disk quota for {user}. "
            f"Currently {used_diskspace} out of {quota} is used. "
            f"Filesize: {filesize}"
        )
        self.file = file
        self.user = user
        self.used_diskspace = used_diskspace
        self.quota = quota
        self.filesize = filesize

def store_file(user, file):
    quota = get_quota(user)
    used_diskspace = get_used_diskspace(user)
    filesieze = get_filesize(file)
    if (used_diskspace + filesize) > quota:
        raise QuotaExceeed(file, user, used_diskspace, quota, filesize)
    store_file_to_disk(user, file)

And finally, exceptions are one of those topics where people like to argue about whether or not they are good. There are valid arguments for both ways. To avoid unnecessary digression and complexity, all I will say is that you could rewrite the code above with if statements to something like this:

def store_file(user, file):
    quota = get_quota(user)
    used_diskspace = get_used_diskspace(user)
    filesieze = get_filesize(file)
    if (used_diskspace + filesize) > quota:
        return (
            f"The file {file} would exceed the disk quota for {user}. "
            f"Currently {used_diskspace} out of {quota} is used. "
            f"Filesize: {filesize}"
        )
    store_file_to_disk(user, file)
    return ""

error_message = store_file(john_doe, uploaded_file)
if error_message:
    print(error_message)