Skip to content Skip to sidebar Skip to footer

Accessing Function Attribute Created In A Decorator Outside That Decorator

I want to count the number of times a given function has been called. So, I made a countcalls decorator to give my functions a __callcount attribute which gets incremented on each

Solution 1:

f.__callcount = [0]

..........


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

......


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

It works. Maybe ther's something more pythonic

Solution 2:

This does what you want. I got it here- https://wiki.python.org/moin/PythonDecoratorLibrary#Alternate_Counting_function_calls

classcountcalls(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 += 1return self.__f(*args, **kwargs)

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

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

@countcallsdeffib(n):
    if n < 0:
        raise ValueError('n must be > 0')
    if n == 0or n == 1:
        return1return 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())) 

Solution 3:

The function object you are adding an attribute to is a different object than the 'original' function. Try this:

import functools

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


@countcallsdeffib(n):
    """fibinacci"""if n < 0:
        raise ValueError('n must be > 0')
    if n == 0or n == 1:
        return1return 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
>>>
"""

Solution 4:

Well, here's the reason, after a bit of help. Thanks guys!

The issue is that functions are immutable. e.g.

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

So, the only way to get the function f we assigned the __callcount attribute to in the definition of countcalls is to return that function from callcount. But we're already returning the inner function _countcalls. We can return both f and _countcalls but that messes up the @countcalls decorator syntax.

You can still do it this way, it's just not as pretty.

import functools

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

deffib(n):
    if n < 0:
        raise ValueError('n must be > 0')
    if n == 0or n == 1:
        return1return 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))

Long story short, just use the class from the Python Decorator Library. :D

Post a Comment for "Accessing Function Attribute Created In A Decorator Outside That Decorator"