Home > Net >  How to change an output?
How to change an output?

Time:12-13

So my task is: Write a decorator that logs information about calls of decorated functions, the values of its arguments, keyword arguments, and execution time. The log should be written to a file.

My solution is:

``
from time import time

def log(func):
    def wrapper(*args, **kwargs):
        start_time = time()
        func(*args, **kwargs)
        end_time = time() - start_time
        with open('log.txt', 'a ') as file:
            file.write(f'{func.__name__}; args: {args}, kwargs: {kwargs}, execution time: {end_time}\n sec.')
    return wrapper
``

I have to test it with a function:

``
@log
def foo(a, b, c):
    ...

foo(1, 2, c=3)
``

And expected outcome in log.txt file must look something like this:

...
foo; args: a=1, b=2; kwargs: c=3; execution time: 0.12 sec.
...

But what I get is:

some_fun; args: (1, 2), kwargs: {'c': 3}, execution time: 0.0
sec.

How should I change my code to get an expected outcome?


CodePudding user response:

You could use signature from the inspect module (standard library) to fetch the variable names, and then use some string manipulations to get the desired output:

from inspect import signature
from time import perf_counter

def log(func):
    params = signature(func).parameters.keys()
    logmsg = func.__name__   "; args: {}; kwargs: {}; execution time: {} sec.\n"
    def wrapper(*args, **kwargs):
        start_time = perf_counter()
        func(*args, **kwargs)
        extime = perf_counter() - start_time
        with open("log.txt", "a ") as file:
            args = ", ".join(f"{name}={value}" for name, value in zip(params, args))
            kwargs = ", ".join(f"{name}={value}" for name, value in kwargs.items())
            file.write(logmsg.format(args, kwargs, extime))
    return wrapper

perf_counter is better for performance measurement.

(In case you're not aware: Your wrapper builds a function that only returns None. If func returns something that the wrapped func should also return, then use result = func(*args, **kwargs) and do return result at the end.)

As pointed out by @zvone: This doesn't capture default arguments. If that is a concern, then you could use the following adjustment (which makes it a bit more complicated):

from inspect import signature
from time import perf_counter

def log(func):
    sig = signature(func)
    logmsg = func.__name__   "; args: {}; kwargs: {}; execution time: {} sec.\n"
    def wrapper(*args, **kwargs):
        start_time = perf_counter()
        func(*args, **kwargs)
        extime = perf_counter() - start_time
        with open("log.txt", "a ") as file:
            binds = sig.bind(*args, **kwargs)
            binds.apply_defaults()
            pairs = list(binds.arguments.items())
            args_str = ", ".join(f"{n}={v}" for n, v in pairs[:len(args)])
            kwargs_str = ", ".join(f"{n}={v}" for n, v in pairs[len(args):])
            file.write(logmsg.format(args_str, kwargs_str, extime))
    return wrapper

Here the default arguments that aren't given in args or kwargs get added to kwargs with their default value (only for the purpose of logging).

CodePudding user response:

For now i think it's not possible, because what are you doing is displaying full {kwargs} and {args}, and they will only return a list.

Solution for this is, that you will write a for loop and display each item from {args} and {kwargs}, and for time, you shoul use another function not

start_time = time();

find other function in the python docs: https://docs.python.org/3/library/time.html#module-time

  • Related