Reputation: 3193
Why is this decorator with a parameter not working?
def decAny( f0 ):
def wrapper( s0 ):
return "<%s> %s </%s>" % ( any, f0(), any )
return wrapper
@decAny( 'xxx' )
def test2():
return 'test1XML'
print( test2() )
always gives me an error saying "str is not callable" it is trying to execute the return string inside the wrapper() instead of processing it and return the result string
Upvotes: 7
Views: 10483
Reputation: 546
there is another to implement decorator using class and you can also pass arguments to the decorator itself
here is an example of a logger helper decorator, which you can pass the function scope and the returned value when it fails
import logging
class LoggerHelper(object):
def __init__(self, scope, ret=False):
self.scope = scope
self.ret = ret
def __call__(self, original_function):
def inner_func(*args, **kwargs):
try:
logging.info(f"*** {self.scope} {original_function.__name__} Excuting ***")
return original_function(*args, **kwargs)
logging.info(f"*** {self.scope} {original_function.__name__} Executed Successfully ***")
except Exception as e:
logging.error(f"*** {self.scope} {original_function.__name__} Error: {str(e)} ***")
return self.ret
return inner_func
and when you use it, you can easily track where the exception was raised
class Example:
@LoggerHelper("Example", ret=False)
def method:
print(success)
return True
Upvotes: 0
Reputation: 1796
There is a good sample from "Mark Lutz - Learning Python" book:
def timer(label=''):
def decorator(func):
def onCall(*args): # Multilevel state retention:
... # args passed to function
func(*args) # func retained in enclosing scope
print(label, ... # label retained in enclosing scope
return onCall
return decorator # Returns the actual decorator
@timer('==>') # Like listcomp = timer('==>')(listcomp)
def listcomp(N): ... # listcomp is rebound to new onCall
listcomp(...) # Really calls onCall
Upvotes: 1
Reputation: 208405
Decorators are functions that return functions. When "passing a parameter to the decorator" what you are actually doing is calling a function that returns a decorator. So decAny()
should be a function that returns a function that returns a function.
It would look something like this:
import functools
def decAny(tag):
def dec(f0):
@functools.wraps(f0)
def wrapper(*args, **kwargs):
return "<%s> %s </%s>" % (tag, f0(*args, **kwargs), tag)
return wrapper
return dec
@decAny( 'xxx' )
def test2():
return 'test1XML'
Example:
>>> print(test2())
<xxx> test1XML </xxx>
Note that in addition to fixing the specific problem you were hitting I also improved your code a bit by adding *args
and **kwargs
as arguments to the wrapped function and passing them on to the f0
call inside of the decorator. This makes it so you can decorate a function that accepts any number of positional or named arguments and it will still work correctly.
You can read up about functools.wraps()
here:
http://docs.python.org/2/library/functools.html#functools.wraps
Upvotes: 16