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.
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)`
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 print
s, 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
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
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.