统计一个方法中另一个方法的调用次数

13 投票
3 回答
11146 浏览
提问于 2025-04-15 13:43

其实我现在在用Java做这个,但我正在自学Python,这让我想知道有没有简单或者聪明的方法可以用包装器之类的来实现这个功能。

我想知道在一个方法里,另一个特定方法被调用了多少次。例如:

def foo(z):
    #do something
    return result

def bar(x,y):
    #complicated algorithm/logic involving foo
    return foobar

所以对于每次调用bar方法,传入不同的参数,我想知道foo方法被调用了多少次,输出可能是这样的:

>>> print bar('xyz',3)
foo was called 15 times
[results here]
>>> print bar('stuv',6)
foo was called 23 times
[other results here]

补充一下:我意识到我可以在bar方法里加个计数器,然后在返回的时候把它输出,但如果能用一些神奇的包装器来实现同样的效果,那就太酷了。这也意味着我可以在其他地方重复使用这些包装器,而不需要修改方法内部的任何代码。

3 个回答

2

根据你的回复,这里有一种使用装饰器工厂的方法...

import inspect

def make_decorators():
    # Mutable shared storage...
    caller_L = []
    callee_L = []
    called_count = [0]
    def caller_decorator(caller):
        caller_L.append(caller)
        def counting_caller(*args, **kwargs):
            # Returning result here separate from the count report in case
            # the result needs to be used...
            result = caller(*args, **kwargs)
            print callee_L[0].__name__, \
                   'was called', called_count[0], 'times'
            called_count[0] = 0
            return result
        return counting_caller

    def callee_decorator(callee):
        callee_L.append(callee)
        def counting_callee(*args, **kwargs):
            # Next two lines are an alternative to
            # sys._getframe(1).f_code.co_name mentioned by Ned...
            current_frame = inspect.currentframe()
            caller_name = inspect.getouterframes(current_frame)[1][3]
            if caller_name == caller_L[0].__name__:
                called_count[0] += 1
            return callee(*args, **kwargs)
        return counting_callee

    return caller_decorator, callee_decorator

caller_decorator, callee_decorator = make_decorators()

@callee_decorator
def foo(z):
    #do something
    return ' foo result'

@caller_decorator
def bar(x,y):
    # complicated algorithm/logic simulation...
    for i in xrange(x+y):
        foo(i)
    foobar = 'some result other than the call count that you might use'
    return foobar


bar(1,1)
bar(1,2)
bar(2,2)

这是输出结果(在Python 2.5.2中测试过):

foo was called 2 times
foo was called 3 times
foo was called 4 times
7

这段话的意思是:这里定义了一个装饰器来实现这个功能:

def count_calls(fn):
    def _counting(*args, **kwargs):
        _counting.calls += 1
        return fn(*args, **kwargs)
    _counting.calls = 0
    return _counting

@count_calls
def foo(x):
    return x

def bar(y):
    foo(y)
    foo(y)

bar(1)
print foo.calls
22

这听起来几乎是装饰器的教科书例子!

def counted(fn):
    def wrapper(*args, **kwargs):
        wrapper.called += 1
        return fn(*args, **kwargs)
    wrapper.called = 0
    wrapper.__name__ = fn.__name__
    return wrapper

@counted
def foo():
    return

>>> foo()
>>> foo.called
1

你甚至可以用另一个装饰器来自动记录一个函数在另一个函数里面被调用了多少次:

def counting(other):
    def decorator(fn):
        def wrapper(*args, **kwargs):
            other.called = 0
            try:
                return fn(*args, **kwargs)
            finally:
                print '%s was called %i times' % (other.__name__, other.called)
        wrapper.__name__ = fn.__name__
        return wrapper
    return decorator

@counting(foo)
def bar():
    foo()
    foo()

>>> bar()
foo was called 2 times

不过,如果 foobar 可能会互相调用,那你就需要一个更复杂的解决方案,涉及到栈来处理这种递归情况。这样的话,你就要开始使用更高级的性能分析工具了...

可能这种包装的装饰器用来做一些神奇的事情,并不是你在‘自学Python’时最理想的方向!

撰写回答