2014-01-08 79 views
2

我想计算给定函数被调用的次数。访问在装饰器之外的装饰器中创建的函数属性

所以,我做了一个countcalls修饰器给我的功能__callcount属性,每增加一个电话。够简单。

我的问题是得到__callcount值后面退出。

这里是我的代码:

import functools 

def countcalls(f): 
    f.__callcount = 0 

    @functools.wraps(f) 
    def _countcalls(*args, **kwds): 
     f.__callcount += 1 
     print(' Called {0} time(s).'.format(f.__callcount)) 
     return f(*args, **kwds) 
    return _countcalls 

@countcalls 
def fib(n): 
    if n < 0: 
     raise ValueError('n must be > 0') 
    if n == 0 or n == 1: 
     return 1 

    return fib(n-1) + fib(n-2) 

if __name__ == '__main__': 
    print('Calling fib(3)...') 
    x = fib(3) 
    print('fib(3) = {0}'.format(x)) 

    print('Calling fib(3) again...') 
    x = fib(3) 
    print('fib(3) = {0}'.format(x)) 

    print('fib was called a total of {0} time(s).'.format(fib.__callcount)) 

产生以下输出(Python的3.3.0):

Calling fib(3)... 
    Called 1 time(s). 
    Called 2 time(s). 
    Called 3 time(s). 
    Called 4 time(s). 
    Called 5 time(s). 
fib(3) = 3 
Calling fib(3) again... 
    Called 6 time(s). 
    Called 7 time(s). 
    Called 8 time(s). 
    Called 9 time(s). 
    Called 10 time(s). 
fib(3) = 3 
fib was called a total of 0 time(s). 

为什么在最后一行fib.__callcount等于0?如输出所示,__callcount得到递增,并在fib的调用之间持续存在。

我错过了什么?

+0

我认为这是一个范围/上下文问题。 http://stackoverflow.com/a/1753475/2823755可能会解释此行为。 – wwii

+0

好找。我只是尝试使用'setattr(f,'__callcount',0)'和'getattr(f,'__callcount')',但它们表现出与原始行为完全相同的行为。 'f .__ dict __ ['__ callcount']'也是同样的行为。 – Keeler

回答

0

好吧,这是原因,在一些帮助后。多谢你们!

问题是函数是不可变的。例如

>>> def f(func): 
...  return func() 
... 
>>> def g(): 
...  return 'sunflower seeds' 
... 
>>> id(g) 
139636515497336 
>>> g = f(g) 
>>> id(g) 
139636515515112 

因此,要获得功能f的唯一途径,我们分配__callcount属性中的countcalls的定义是从callcount返回功能。但是我们已经返回了内部函数_countcalls。我们可以返回f_countcalls,但是这会弄乱@countcalls装饰器语法。

你仍然可以这样做,它只是不那么漂亮。

import functools 

def countcalls(f): 
    f.__callcount = 0 

    @functools.wraps(f) 
    def _countcalls(*args, **kwds): 
     f.__callcount += 1 
     print(' Called {0} time(s).'.format(f.__callcount)) 
     return f(*args, **kwds) 
    return f, _countcalls 

def fib(n): 
    if n < 0: 
     raise ValueError('n must be > 0') 
    if n == 0 or n == 1: 
     return 1 

    return fib(n-1) + fib(n-2) 

if __name__ == '__main__': 
    counter, fib = countcalls(fib) 

    print('Calling fib(3)...') 
    x = fib(3) 
    print('fib(3) = {0}'.format(x)) 

    print('Calling fib(3) again...') 
    x = fib(3) 
    print('fib(3) = {0}'.format(x)) 

    print('fib was called a total of {0} time(s).'.format(counter.__callcount)) 

长话短说,只是使用class from the Python Decorator Library。 :D

0
f.__callcount = [0] 

.......... 


f.__callcount[0] = f.__callcount[0] + 1 

...... 


print('fib was called a total of {0} time(s).'.format(fib.__callcount[0])) 

它的工作原理。
也许它的东西更pythonic

+0

这绝对有效,它基本上就是我想要的。但这很奇怪......如果将'f .__ callcount = [0]'改为'f .__ callcount = {0:0}',它也可以工作。为什么这与列表和字典一起工作,但不是一个整数?我要编辑我的问题来澄清。 – Keeler

+0

可变性可能与它有关 - 列表和字典是可变的。该列表是在函数定义(修饰)处创建的,并且类似于使用可变对象作为函数参数的默认值,它将进入全局范围。 – wwii

0

这就是你想要的。我知道了这里 - https://wiki.python.org/moin/PythonDecoratorLibrary#Alternate_Counting_function_calls

class countcalls(object): 
    "Decorator that keeps track of the number of times a function is called." 

    __instances = {} 

    def __init__(self, f): 
     self.__f = f 
     self.__numcalls = 0 
     countcalls.__instances[f] = self 
     self.__doc__ = f.func_doc 
     self.__name__ = f.func.func_name 

    def __call__(self, *args, **kwargs): 
     self.__numcalls += 1 
     return self.__f(*args, **kwargs) 

    def count(self): 
     "Return the number of times the function f was called." 
     return countcalls.__instances[self.__f].__numcalls 

    @staticmethod 
    def counts(): 
     "Return a dict of {function: # of calls} for all registered functions." 
     return dict([(f.__name__, countcalls.__instances[f].__numcalls) for f in countcalls.__instances]) 

@countcalls 
def fib(n): 
    if n < 0: 
     raise ValueError('n must be > 0') 
    if n == 0 or n == 1: 
     return 1 

    return fib(n-1) + fib(n-2) 

if __name__ == '__main__': 
    print('Calling fib(3)...') 
    x = fib(3) 
    print('fib(3) = {0}'.format(x)) 
    print('fib was called a total of {0} time(s).'.format(fib.count())) 

    print('Calling fib(3) again...') 
    x = fib(3) 
    print('fib(3) = {0}'.format(x)) 

    print('fib was called a total of {0} time(s).'.format(fib.count())) 
+0

是的,我从这段确切的代码中得到了灵感。 :) 我想使用functools.wraps和一个单一的属性来保持简单,当我试图添加另一个装饰器到相同的功能由于某种原因,countcalls装饰器给我麻烦。 – Keeler

+0

在您的帮助下,我将此问题的解释发布为[此答案](http://stackoverflow.com/a/20987591/3171324)。这让我疯狂,谢谢你的帮助。将你的标记标记为答案,因为无论如何我都链接到这个例子。 – Keeler

0

的函数对象要添加的属性是不同的对象比“原始”功能。试试这个:

import functools 

def countcalls(f): 
    f.__callcount = 0 

    @functools.wraps(f) 
    def _countcalls(*args, **kwds): 
     f.__callcount += 1 
     print 'id(f):', id(f) 
     print(' Called {0} time(s).'.format(f.__callcount)) 
     return f(*args, **kwds) 
    return _countcalls 


@countcalls 
def fib(n): 
    """fibinacci""" 
    if n < 0: 
     raise ValueError('n must be > 0') 
    if n == 0 or n == 1: 
     return 1 

    return fib(n-1) + fib(n-2) 


if __name__ == '__main__': 
    print('Calling fib(3)...') 
    x = fib(3) 
    print 'id(fib):', id(fib) 

""" 
>>> 
Calling fib(3)... 
id(f): 45611952 
    Called 1 time(s). 
id(f): 45611952 
    Called 2 time(s). 
id(f): 45611952 
    Called 3 time(s). 
id(f): 45611952 
    Called 4 time(s). 
id(f): 45611952 
    Called 5 time(s). 
id(fib): 45612016 
>>> 
"""