3

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.

8
  • "on that line" on which line? Commented Mar 10, 2014 at 17:19
  • 1
    Before "that line", What is fcount? Commented Mar 10, 2014 at 17:20
  • 1
    'NoneType' object has no attribute 'count' means f or g is None, not the attribute. Python functions are objects; they can have attributes just like many other object types. Commented Mar 10, 2014 at 17:22
  • Added some details to answer those questions. Commented Mar 10, 2014 at 17:23
  • Is this just a roundabout way of asking, "how do I construct a decorator that counts how many times a function has been called, and how do I access that number?"? Commented Mar 10, 2014 at 17:23

3 Answers 3

7

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)`
Sign up to request clarification or add additional context in comments.

Comments

4

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

6 Comments

Oh, so the decorator function has to return a function, which then becomes the value of the function that called the decorator. And you make another function inside the decorator function, with the *args and **kwargs, that does the actual work you want done, in this case, incrementing a counter?
That's right - you create a new function (func) inside the decorator function (fcount), which in turn (usually) calls the argument function (f) with whatever arguments it is passed (args, kwargs). This new function is returned by the decorator to replace the original function.
What if I wanted to put it in a class, rather than a standalone function? Would I just put the whole thing directly inside a header like: class fcount2(object):
I'm not sure exactly what you're asking. You can decorate class or instance methods, but the decorator would still be a standalone function, not itself a class.
Yeah, I guess I was asking if classes can be decorated, too, or just functions.
|
0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.