访问在装饰器外部的装饰器中创建的函数属性

时间:2014-01-08 02:25:28

标签: python function attributes decorator python-decorators

我想计算一个给定函数被调用的次数。

所以,我做了一个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 v3.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的调用之间保持不变。

我错过了什么?

4 个答案:

答案 0 :(得分: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

答案 1 :(得分: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())) 

答案 2 :(得分: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
>>>
"""

答案 3 :(得分:0)

嗯,经过一些帮助,这就是原因。谢谢你们!

问题是函数是不可变的。 e.g。

>>> 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