Search code examples
pythonperformancef-string

Can I delay evaluation of the python expressions in my f string?


I have a debug logging method in my program that just prints the passed in string to sys.stderr if debug is True:

def debug_log(str):
    if debug:
        print(str, file=sys.stderr)

However, I'm currently passing in some f strings containing expressions that would be time-consuming computations once I go from my debugging data set with a few hundred items to my production dataset with a few million items. If I have a line in my code like:

debug_log(f'{sum([author_dict[a].get("count") for a in author_dict.keys()])} count total in author_dict')

am I correct to assume that that sum is going to get computed when I call debug_log, rather than only happening inside debug_log when it goes to actually do the print statement? And if that's the case is there a clean way to set this up so that it doesn't do the computation when debug is False? (Otherwise I'll just comment out the lines where that's going to matter before I run in production)


Solution

  • As pointed out in the comments, arguments are evaluated eagerly, f-strings included. To make the evaluation lazy, The logging functions accept *args and **kwargs for example, as explained here.


    Now, a clean way would be using strings that are subsequently format()ed instead of f-strings, but if f-string are just too much convenient you can still use lambdas (only when lazy-evaluation is needed). You could change the debug_log function in something like this:

    def debug_log(log):
        if not debug: return
        str_log = log() if callable(log) and log.__name__ == '<lambda>' else log
        print(str_log, file=sys.stderr)
    

    A few examples should make everything more clear.


    Eager evaluation is good

    If you need to print an argument that does not need to be lazily evaluated, then nothing is different from before:

    >>> debug = False
    >>> debug_log('hello')
    >>> debug = True
    >>> debug_log('hello')
    hello
    

    Lazy evaluation necessary

    Now suppose you have a function like this one:

    def test():
        print('please print me if necessary')
        return 'done'
    

    If called inside debug_log as-is, then

    >>> debug = False
    >>> debug_log(test())
    please print me if necessary
    

    But, of course, you do not want to see please print me if necessary because debug = False. So, the clean way would be:

    >>> debug = False
    >>> debug_log(lambda: f'we are {test()}')
    >>> debug = True
    >>> debug_log(lambda: f'we are {test()}')
    please print me if necessary
    we are done
    

    Basically, by enclosing an f-string inside a lambda, you can be sure that it is executed only when the lambda is actually called.