Reputation: 189
def tracer(func, enabled=True):
def wrap(*args, **kwargs):
if enabled:
print('Calling {}'.format(func))
return func(*args, **kwargs)
return wrap
@tracer(enabled=False)
def rotate_list(l):
return l[1:] + [l[0]]
I'm having a bit of confusion as to why this doesn't work, specifically this part: @tracer(enabled=False)
What I understand is happening here is:
The function object rotate_list
is passed as an argument whenever tracer
performs a call.
I think the reason this doesn't work is because tracer(and any wrapper for that matter) only accepts callable objects, enabled=False
isn't a callable so it doesn't work.
The error message however is not very indicative of that, so I'm wondering why the error message was:
TypeError: tracer() missing 1 required positional argument: 'func'
I guess the arguments inside the parentheses were evaluated first, such that no callable object was passed to tracer?
I guess this can be solved by using class decorators, doing something like
class Trace:
def __init__(self, enabled=False):
print('Inside __init__')
self.enabled = enabled
then tracer = Trace(enabled=True)
would work, but I'd like to see how this can be solved without using classes.
============
EDIT (Solution): Don't mind this, just typing it out to ensure I understood the solution. Since placing an argument inside a decorator makes it act like a normal function. The solution would be to make that decorator return another callable object (which is the actual decorator).
Like:
@dec
def foo: pass
would become foo = dec(foo)
@dec(ARG)
def foo: pass
would become foo = dec(ARG)(foo)
The solution would be to make dec
return another callable which is the actual decorator. For example that function would be wrap
foo = dec(ARG)(foo)
would become foo = wrap(foo)
where ARG
was already passed to dec
.
Thanks guys! I love functional programming.
Upvotes: 0
Views: 226
Reputation: 1856
To pass other parameters than the function to a decorator-function you nest multiple defs:
def my_decorator(flagDoThat)
def internal(func):
def wrapper(*argv, **kwv):
retval = func(*argv, **kwv)
if flagDoThat:
print("Hello", retval)
return retval
wrapper.__name__ = func.__name__ #update name
return wrapper
return internal
@my_decorator(True)
def my_func(): return "world"
#equals to
tmp = my_decorator(True)
@tmp
def my_func(): return "world"
This decorator helps to build other decorators. While this contains multiple nested functions, it allows you to define decorators with only two layers and arguments, like you did:
def decorator(keepFunctionName=True):
def internal(func):
def newFunc(*argv, **kwv):
def decoWrapper(theFuncUsedInFunc):
fRet = func(theFuncUsedInFunc, *argv, **kwv)
if keepFunctionName:
fRet.__name__ = theFuncUsedInFunc.__name__
return fRet
return decoWrapper
return newFunc
return internal
And can be used like this:
@decorator()
def my_decorator(func, flagDoThat):
def wrapper(*argv, **kwv):
retval = func(*argv, **kwv)
if flagDoThat:
print("Hello", retval)
return retval
return wrapper
And this decorator does exactly that what the decorator above does.
You can do the same thing with classes, by attaching the decorator to the init-function.
But here is an other way you can make decorators with parameters by storing them in classes:
class my_decorator:
__slots__ = ["flagDoThat"] # optional line
def __init__(self, flagDoThat):
self.flagDoThat = flagDoThat
def __call__(self, func):
def wrapper(*argv, **kwv):
retval = func(*argv, **kwv)
if self.flagDoThat:
print("Hello", retval)
return retval
return wrapper
Upvotes: 1
Reputation: 3654
It can be done only with functions. tracer
should look like this:
def tracer(enabled=False):
def wrapper(func):
def wrap(*args, **kwargs):
if enabled:
print('Calling {}'.format(func))
return func(*args, **kwargs)
return wrap
return wrapper
What is done here is that you created function (tracer) that returns decorator. That decorator accepts a function.
If you translate it to same format as python does for any decorator, you will see why this is required.
Every time python sees
@dec
def foo(): pass
it translates it to:
def foo(): pass
foo = dec(foo)
So, when you have decorator that requires arguments, it is translated like this:
foo = dec(ARGS)(foo)
So, what you need to do is make sure that decorator returns something that accepts function as its parameter.
PS: It is nice to use functools.wraps for decorator to preserve function name, docstrings, etc.
Upvotes: 1