Sometimes when debugging we like to print/log out all the inputs and outputs of a method: name, args, kwargs, dict etc.

We would have some IDE debug features for this purpose but sometimes we need manual debugging.

And while manual debugging I don’t want to write logger.debug(<messsage>) to every one of two lines of a module.

The solution : Python Decorators

First configure a logger. In this post I will implement a default basic configuration. For a detailed configuration go to the post logging configuration post .

Then crate a module for debuggers in utils package, utils/debuggers.py like below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# utils/debuggers.py

import logging


logging.basicConfig(
    level=logging.DEBUG,
    format="[%(asctime)s] [%(levelname)s] %(name)s: %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S %z",
)
logger = logging.getLogger(__name__)


class Debugger:
    """ Debug a method and return it back"""

    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        logger.debug(f'Entering : {self.func.__name__}')
        logger.debug(f'args, kwargs : {args, kwargs}')

        result = self.func(*args, **kwargs)

        logger.debug(f'{self.func.__name__} returned : {result}')

        return result

Then call it and decorate your function like below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# main.py

from utils.debuggers import Debugger


@Debugger
def my_func(a, b, c, d):
    return a + b + c + d


if __name__ == "__main__":
    kwargs = dict(
        a=1,
        b=2,
        c=5,
        d=-10
    )

    my_func(**kwargs)

Output will be like:

2019-07-21 18:43:25,635 [DEBUG] debuggers: Entering : my_func
2019-07-21 18:43:25,635 [DEBUG] debuggers: args, kwargs: ((), {'a': 1, 'b': 2, 'c': 5, 'd': -10})
2019-07-21 18:43:25,635 [DEBUG] debuggers: my_func returned -2

If you want a function instead of a class for debugging:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# utils/decorators.py

import functools
import logging


logging.basicConfig(
    level=logging.DEBUG,
    format="[%(asctime)s] [%(levelname)s] %(name)s: %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S %z",
)
logger = logging.getLogger(__name__)


def debugmethod(func):
    """ Debug a method and return it back"""

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        logger.debug(f'Calling : {func.__name__}')
        logger.debug(f'args, kwargs: {args, kwargs}')

        result = func(*args, **kwargs)

        logger.debug(f'{func.__name__} returned {result}')

        return result

    return wrapper

Also if you like to know the execution time of the method:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# utils/debuggers.py

import functools
import logging
import time


logging.basicConfig(
    level=logging.DEBUG,
    format="[%(asctime)s] [%(levelname)s] %(name)s: %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S %z",
)
logger = logging.getLogger(__name__)


def timerun(func):
    """ Calculate the execution time of a method and return it back"""

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        duration = time.time() - start

        logger.debug(f"Duration of {func.__name__} function was {duration}")

        return result

    return wrapper

Output:

2019-07-21 18:43:25,636 [DEBUG] debuggers: Duration of my_func was 0.00023937225341796875

You can combine your decorators as below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# main.py

from utils.debuggers import debugmethod, timerun


@timerun
@debugmethod
def my_func(a, b, c, d):
    return a + b + c + d


if __name__ == "__main__":
    kwargs = dict(
        a=1,
        b=2,
        c=5,
        d=-10
    )

    my_func(**kwargs)

Output should be like this:

2019-07-21 18:43:25,635 [DEBUG] debuggers: Calling : my_func
2019-07-21 18:43:25,635 [DEBUG] debuggers: args, kwargs: ((), {'a': 1, 'b': 2, 'c': 5, 'd': -10})
2019-07-21 18:43:25,635 [DEBUG] debuggers: my_func returned -2
2019-07-21 18:43:25,636 [DEBUG] debuggers: Duration of my_func was 0.00023937225341796875

OK, all done.


Changelog

  • 2022-05-11 :
    • Fixed Debugger: multiple func call and possible bug for modified args/kwargs. Thanks @dwysocki for pointing that out.
    • Added basic logging config.
    • Added line numbers to code snippets.