Question

I'm very new to Python and I don't understand how functions themselves can seemingly have attributes. In the code below, there is a function called f, and later in the code, something by the name of f.count is referenced. How can a function, namely f, have a .count? I'm getting an error message of: 'NoneType' object has no attribute 'count' on that line, so it obviously doesn't have that attribute yet. How do I give it that attribute?

def fcount(n):
    print n.__name__


@fcount
def f(n):
    return n+2

for n in range(5):
    print n
    #print f(n)

print 'f count =',f.count #THE LINE CAUSING THE ERROR MENTIONED ABOVE

@fcount
def g(n):
    return n*n

print 'g count =',g.count
print g(3)
print 'g count =',g.count

Edit: Added fcount(), which doesn't do anything much, and details about error.

Was it helpful?

Solution

Let’s start with the definition of f:

@fcount
def f(n):
    return n+2

This defines a f as the return value of a call to the function fcount, which is used as a decorator (the leading @) here.

This code is roughly equivalent with

def f(n):
    return n+2

f = fcount(f)

since the decorator – fcount – does not return anything, f is None and not a function at the call site.

In your case fcount should return some function and add a count attribute to that returned function. Something useful (?) might be

def fcount(fn):
    def wrapper(n):
        wrapper.count += 1
        return fn(n)
    wrapper.count = 0
    return wrapper

EDIT

As @jonrsharpe pointed out, a generalized decorator can forward positional and keyword arguments by capturing them with *args and **kwargs in the signature and expanding them in the same way when calling another function. The names args and kwargs are used by convention.

Python also has a helper function (a decorator itself) that can transfer information (name, docstring and signature information) from one function to another: functools.wraps. A complete example looks like this:

from functools import wraps

def decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@decorator
def f(a, b, c=None):
   "The useful f function"
   pass

print f.__name__ # `f` rather than `wrapper`
print help(f) # `f(*args, **kwargs) The useful f function` rather than `wrapper(*args, **kwargs)`

OTHER TIPS

The problem here is with your "decorator function", fcount. A Python decorator function should return a function:

@decorates
def func(...):
    ...

is effectively:

func = decorates(func)

In your case, the "decorator" fcount only prints, and won't return anything; hence using it will assign f = None.

The answer to your more general question is that functions, being Python objects like more-or-less everything else, certainly can have attributes. To actually implement what you want, a decorator that counts how many times the decorated function is called, you could do:

def fcount(f):
    """Decorator function to count how many times f is called."""
    def func(*args, **kwargs):
        func.count += 1
        return f(*args, **kwargs)
    func.count = 0
    return func

Python functions are first-class objects.

They can have attributes.

This feature is rarely seen in the literature, but it has its uses, such as a simplified closure.

By the way, the kind of counter code you asked about is explained in a Pycon 2014 video about decorators.

Reference

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top