-2

let's say I have 3 functions a(), b(), and c() such that a calls b, and b in turn calls c. for eg:

def a():
    b()

def b():
    c()

def c():
   do something else

Now I want to time a such that I want to leave out whatever c does. Please suggest an answer that is not a hack - that is not by just timing separately what c does. I'm planning to use a decorator

I want to know a solution like this would be possible (basically something to replace the if statement with)

from functools import wraps
from time import time

def timing(f):
    @wraps(f)
    def wrap(*args, **kw):

      if the current function is not c - then start time:
        ts = time()
        result = f(*args, **kw)
        te = time()
        return result
    return wrap
AMC
  • 2,642
  • 7
  • 13
  • 35
  • 1
    What have you tried so far? – colidyre Apr 24 '20 at 00:27
  • 2
    " Please suggest an answer that is not a hack - that is not by just timing separately what c does." That doesn't sound like a hack to me. Timing `a()` and subtracting `c()` sounds like a fine idea. – juanpa.arrivillaga Apr 24 '20 at 00:28
  • @juanpa.arrivillaga: sure, hack maybe a wrong word but it will only work for this problem. I want a generic solution where I can use a decorator like this - https://stackoverflow.com/a/27737385/11203796 – pandas_noob Apr 24 '20 at 00:34
  • @colidyre: I feel genuinely stuck. I looked into the inspect module to help extract the method name, but couldn't make much progress – pandas_noob Apr 24 '20 at 00:35
  • @pandas_noob what exactly would a generic solution look like? Can you give an example of what you are trying to achieve in your question? It sounds to me like you want to be able to do something like `@time(ignore='c')` or something, that would involve insane amounts of digging into the guts of the function object, probably not even worth attempting. – juanpa.arrivillaga Apr 24 '20 at 00:43
  • @juanpa.arrivillaga - I edited the question, does it not make things any clear? "that would involve insane amounts of digging into the guts of the function object, probably not even worth attempting. " - this is what I fear. I think it may not probably be worth it then. But I've gone through inspect module and maybe it could be done? Which is why I posted the question here – pandas_noob Apr 24 '20 at 00:45
  • The "subtract out sub-functions" approach can be made general as long as you're willing to decorate all the sub-functions as well. – Karl Knechtel Apr 24 '20 at 00:47
  • @KarlKnechtel yes indeed. – juanpa.arrivillaga Apr 24 '20 at 00:52
  • @KarlKnechtel - can you please expand on it a little? – pandas_noob Apr 24 '20 at 01:05
  • @pandas_noob - see my answer, it outlines the "so long as you're willing to decorate all sub-functions". As I explain, it is for all intents and purposes not possible to do what you're asking any other way. One decorator on the main function, and another for any sub-functions you wish to exclude from the timing. Pretty clean. – Charles Angus Apr 28 '20 at 03:19

1 Answers1

1

This is not possible, at least not in any kind of clean way which only touches a(). a() and b() could contain arbitrary code, which a()'s decorator would not know about. What if you add d() to b()? How would the decorator know whether to include d() in the timing or not?

You could make a global timer which is started and stopped, and decorate a() with a @start_timer decorator, and then decorate c() with a @stop_timer_and_restart_after decorator. (Edit: this is the "decorate subfunctions as well" option suggested in the comments; wrote this before those comments were added.)

Working example using decorators to define which functions to time and which not to:

import time
import timeit

class Timer(object):

    def __init__(self):
        self.time_accumulator = 0
        self.initial_time = 0
        self.current_time = 0

    def start_timing(self):
        self.initial_time = timeit.default_timer()

    def stop_timing(self):
        current_time = timeit.default_timer()
        elapsed = current_time - self.initial_time
        self.time_accumulator += elapsed

timer = Timer()

def timing_decorator(func):
    def wrapper():
        global timer
        timer.start_timing()
        func()
        timer.stop_timing()
        print(timer.time_accumulator)
    return wrapper

def stop_and_restart_timing_decorator(func):
    def wrapper():
        global timer
        timer.stop_timing()
        func()
        timer.start_timing()
    return wrapper

@timing_decorator
def a():
    time.sleep(.1)
    b()

def b():
    print("in b")
    time.sleep(.2)
    c()

@stop_and_restart_timing_decorator
def c():
    time.sleep(.3)
    print("in c")

a()

Total time on timer is about .3 seconds, as expected.

Charles Angus
  • 394
  • 1
  • 7