A caller id for your python function

Most of you might be too young to know this but there was time that the phone in your house – not in your pocket! – would ring and gasp! you had no idea who were calling! You had to ANSWER the phone to find out. 🤦‍♀️ Now we have caller ID and know exactly who is calling – and can completely ignore the call decide if we are answering the call or not.

When working with large applications, for debugging purposes, I find very helpful to know which function is calling what function. Breakpoints and print statements are useful tools but sometimes you just need more information. In a large codebase it’s fairly easy to get lost with breakpoints so I’ve been using the chunk of code below to print extra info and learn a little bit more about my application.

import inspect
func = inspect.currentframe().f_back.f_code
print(f"in function 'my_function' called from NAME: {func.co_name} in FILENAME: {func.co_filename} on LINE#: {func.co_firstlineno}")

How to use

Consider a *very* simple program (which honestly doesn’t even need our helper function, but for the sake of example, stay with me here):

def helper_function(value):
    return value.isnumeric()

def adder(num1, num2):
    return int(num1) + int(num2)

def main():
    print("The most useless calculator!")
    first_number = input("Please enter a number: ")
    second_number = input("Please enter another number: ")
    print(f"Let's add {first_number} and {second_number}")

    if helper_function(first_number) and helper_function(second_number):
        total = adder(first_number, second_number)
        print(f"The total is {total}")
    else:
        print("Sorry, we cannot add these values...")

main()

The function above takes two pieces of input form the command line (lines 11 and 12), calls a helper function to check if the values entered are numbers (line 15) and if so, calls the function to add the numbers (line 16).

Now, just for a moment let’s pretend that each of these functions are way more complicated than this and live in separate files. Add some more functionality, a framework, 2 cups of all-purpose flour and a webserver. Voilà! You have a web application. What if you still want to know which function called what? Let’s add our chunk of code to adder:

def helper_function(value):
    return value.isnumeric()

def adder(num1, num2):
    import inspect
    func = inspect.currentframe().f_back.f_code
    print(
        f" **** in function 'adder' called from NAME: {func.co_name} in FILENAME: {func.co_filename} on LINE#: {func.co_firstlineno}"
    )

    return int(num1) + int(num2)

def main():
    print("The most useless calculator!")
    first_number = input("Please enter a number: ")
    second_number = input("Please enter another number: ")
    print(f"Let's add {first_number} and {second_number}")

    if helper_function(first_number) and helper_function(second_number):
        total = adder(first_number, second_number)
        print(f"The total is {total}")
    else:
        print("Sorry, we cannot add these values...")

main()

When you run this script now (let’s save it as “watcher.py”), it will output the information about the function that called adder, in this case, main:

>>> python3 watcher.py 
The most useless calculator!
Please enter a number: 4
Please enter another number: 2
Let's add 4 and 2
**** in function 'adder' called from NAME: main in FILENAME: watcher.py on LINE#: 15
The total is 6

Note that the line number is the line where the parent function, in this case “main”, starts, not where our function was called from!

This has been very helpful and I’ve used a lot lately. I hope it helps you too!

Fancypants 👖

You will need to copy this chunk of code into every function you would like to debug but that’s a bit tedious. Instead, you can turn it into a decorator and decorate the functions you want to inspect. Start by creating your decorator:

def caller_id(my_func):
    def wrapper(*args, **kwargs):
        import inspect

        my_func(*args, **kwargs)

        func = inspect.currentframe().f_back.f_code
        print(
            f"in function {my_func. __name__ } called from NAME: {func.co_name} in FILENAME: {func.co_filename} on LINE#: {func.co_firstlineno}"
        )

    return wrapper

Then, you can decorate your function with it. Let’s add it to adder again. Now adder looks like:

@caller_id
def adder(num1, num2):
    return int(num1) + int(num2)

That’s it.

If you found this helpful, let me know on Twitter!

The post A caller id for your python function _was originally published at _flaviabastos.ca

21