Don't Try This at Work: Exception Driven Fizz Buzz
Tags: python
Published: 2020-06-27 12:58:14

So, I was catching up on some of the talks from PyCon 2020. First, I watched [Steven Lott - Type Hints: Putting more Buzz in your Fizz](https://www.youtube.com/watch?v=udVRTBm_-q0) and then [Elizaveta Shashkova - The Hidden Power of the Python Runtime](https://www.youtube.com/watch?v=yr6E7FwK_Hw). I think both talks were really interesting, however, together they inspired me to write this abomination of Fizz buzz implementation. If you haven't heard about Fizz buzz, [see this Wikipedia page](https://en.wikipedia.org/wiki/Fizz_buzz).

# Background
So from Elizavetas talk, I learned a bit about how Python stack frames are easily accessible at runtime. Most importantly for this post, when you capture an exception using the `except`-keyword you can get the current stack frame from the traceback in the exception.
```python
try:
    raise ValueError()
except ValueError as e:
    tb = e.__traceback__
    current_stack_frame = tb.tb_frame
```
After experimenting a bit I also found that you can use `tb.tb_next`, to get the next traceback object in the chain. So, if you iterate until there is no tb_next then you have reached the bottom of the trace. This is where the error was raised.

## Implementation
Now let's get to the actual implementation. First, we will define three functions, fizz, buzz and fizzbuzz:
```python
def fizzbuzz(val):
    if val % 3 == 0 and val % 5 == 0: 
        raise ValueError()
    fizz(val)

def fizz(val):
    if val % 3 == 0: 
        raise ValueError()
    buzz(val)

def buzz(val):
    if val % 5 == 0: 
        raise ValueError()
```
`fizzbuzz` is the only function we will call from another part of the code. Therefore, we can think of that as our entry point to this call stack. In fizzbuzz we do our first check, to see if the value is both divisible by 5 and 3 in which case we should print fizzbuzz. We will get to how we print the values in just a little bit. However, if the value didn't fulfill the condition we call fizz. Here again, the fizz-condition is checked, and if the condition fails it calls buzz which tests the buzz-condition.

Now let's see how we write this to the console:
```python
def exception_driven_fizzbuzz(val):
    try:
        fizzbuzz(val)
        print(val)
    except ValueError as e:
        tb = e.__traceback__
        while tb.tb_next:
            tb = tb.tb_next
        print(tb.tb_frame.f_code.co_name)
```
Given some value, we call fizzbuzz. This triggers the call stack we just looked at. The idea is that the function which raises the exception should have its name written to the console. This is what we do in the except-block of this function. If none of the functions in the call stack raised an error the print-function after the fizzbuzz call will run, writing just the number to the console. As no error then has been raised the except-block isn't evaluated. However, if one the functions do raise an error we will iterate until we reach the last traceback. This traceback corresponds to the function which raised the error. Therefore, we extract the stack frame from this traceback and access it's code-object (`f_code`) which contains a name, `f_name`. `f_name` is the name of the function or module of the code object. In this case this will be either: fizz; buzz or fizzbuzz, depending on which function raised the exception.

Let's take an example to see how this is evaluated. We call the function with `val=3`. This doesn't trigger an error in fizzbuzz, but it does trigger one in fizz. The program goes into the except-block, without evaluating the print in the try-block. The first value of `tb` is the traceback in this function `exception_driven_fizzbuzz`. We take one lap in the loop, tb now correspond to `fizzbuzz`. Then we take a final lap in the loop and tb is now `fizz`. From tb we then extract the name _fizz_ and write it to the console. If, we do the same for `val=2`, this will trigger no error in our call stack. Therefore, just the try-block will be executed, but this time it reaches the print-function.

Now we just need to slap a loop around this and our fizzbuzz is done:
```python
for val in range(1, 16):
    exception_driven_fizzbuzz(val)
```
output:
```
1
2
fizz
4
buzz
fizz
7
8
fizz
buzz
11
fizz
13
14
fizzbuzz
```

## Words of warning
I think this is a fun way to challenge yourself and learn new things about the language. However, I wouldn't do this on my next job interview if I were you.