What's the difference between raise, try, and assert?

I have been learning Python for a while and the raise function and assert are (what I realised is that both of them crash the app, unlike try - except) really similar and I can't see a situation where you would use raise or assert over try.

So, what is the difference between raise, try, and assert?


The statement assert can be used for checking conditions at runtime, but is removed if optimizations are requested from Python. The extended form is:

assert condition, message

and is equivalent to:

if __debug__:
    if not condition:
        raise AssertionError(message)

where __debug__ is True is Python was not started with the option -O.

So the statement assert condition, message is similar to:

if not condition:
    raise AssertionError(message)

in that both raise an AssertionError. The difference is that assert condition, message can be removed from the executed bytecode by optimizations (when those are enabled--by default they are not applied in CPython). In contrast, raise AssertionError(message) will in all cases be executed.

Thus, if the code should under all circumstances check and raise an AssertionError if the check fails, then writing if not condition: raise AssertionError is necessary.


Assert:

Used when you want to "stop" the script based on a certain condition and return something to help debug faster:

list_ = ["a","b","x"]
assert "x" in list_, "x is not in the list"
print("passed") 
#>> prints passed

list_ = ["a","b","c"]
assert "x" in list_, "x is not in the list"
print("passed")
#>> 
Traceback (most recent call last):
  File "python", line 2, in <module>
AssertionError: x is not in the list

Raise:

Two reasons where this is useful:

1/ To be used with try and except blocks. Raise an error of your choosing, could be custom like below and doesn't stop the script if you pass or continue the script; or can be predefined errors raise ValueError()

class Custom_error(BaseException):
    pass

try:
    print("hello")
    raise Custom_error
    print("world")
except Custom_error:
    print("found it not stopping now")

print("im outside")

>> hello
>> found it not stopping now
>> im outside

Noticed it didn't stop? We can stop it using just exit(1) in the except block.

2/ Raise can also be used to re-raise the current error to pass it up the stack to see if something else can handle it.

except SomeError, e:
     if not can_handle(e):
          raise
     someone_take_care_of_it(e)

Try/Except blocks:

Does exactly what you think, tries something if an error comes up you catch it and deal with it however you like. No example since there's one above.


raise - raise an exception.

assert - raise an exception if a given condition is (or isn't) true.

try - execute some code that might raise an exception, and if so, catch it.


Assertions

  • Should only be used for debugging purposes
  • Although similar to Raise/Exceptions they serve different purposes, because they are useful to point scenarios where program error cannot be recovered from
  • Assertions always raise AssertionError exceptions, here's how they work:

syntax: assert_stmt ::= "assert" expression1 ["," expression2]

at execution time it translates to:

if __debug__:
  if not expression1:
    raise AssertionError(expression2)
  • __debug__ is a built-in flag that is usually true, but if optimisations are triggered it will be false, thus assertions will be dead code => disabled with the -O and -OO flags when starting Python (or PYTHONOPTIMIZE env variable in CPython), so, don't rely on them for code logic.
  • Don’t Use Asserts for Data Validation because of previous point
  • A good use case for assertions => make program "explode" if some unexpected state of the program should make it stop under all circumstances => thus, under circumstances where an exception if caught would make program exit altogether.
  • If you have a bug-free program, then assertions will/should never be triggered, they serve as health checks for the program
  • Careful when using a data structures (such as tuples) as the expression1 in assertions that always evaluate to True for non-empty values => the assertions will always be triggered, breaking down program - eg: assert (<some_test>, 'warn string') => notice the tuple construct (wrong!)

Check: Catching bogus Python asserts on CI by Dan Bader

Raise/Exceptions

  • Their purpose is to handle scenarios where program logic is in an exceptional state but you know what logic to recover from that state
  • When you raise an exception, you can make the type of the exception appropriate to the error (better control over semantic value) and catch it later => so you can create multiple exception types that you know how to recover from, and handle them
  • They are a mechanism for handling known/expected scenarios of run-time errors
  • Useful for data validation when using if-statements and raising validation exceptions per scenario

Try

  • Is just a syntactic element of coding exceptions handling

BTW, I highly recommend the book, "Python Tricks: The Book" by Dan Bader (from realpython.com)