Reputation: 27684
I have a decorator like below.
def myDecorator(test_func):
return callSomeWrapper(test_func)
def callSomeWrapper(test_func):
return test_func
@myDecorator
def someFunc():
print 'hello'
I want to enhance this decorator to accept another argument like below
def myDecorator(test_func,logIt):
if logIt:
print "Calling Function: " + test_func.__name__
return callSomeWrapper(test_func)
@myDecorator(False)
def someFunc():
print 'Hello'
But this code gives the error,
TypeError: myDecorator() takes exactly 2 arguments (1 given)
Why is the function not automatically passed? How do I explicitly pass the function to the decorator function?
Upvotes: 143
Views: 114180
Reputation: 2167
Decorator that accept multiple input is like dataclasses
decorator
In that example, dataclass
accept three syntaxes:
@dataclass
class C:
...
@dataclass()
class C:
...
@dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False,
match_args=True, kw_only=False, slots=False, weakref_slot=False)
class C:
...
In creating decorator with same behavior, you can use this,
import inspect
def customdecorator(*args, **kwargs):
def decorator(func):
print('input decorator:', args, kwargs)
def __wrapper(*func_args, **func_kwargs):
print('input decorator inside function:', args, kwargs)
print('input function:', func_args, func_kwargs)
# Do something before calling the function
result = func(*func_args, **func_kwargs)
# Do something after calling the function
return result
return __wrapper
print('input root:', args, kwargs)
if len(kwargs) > 0:
# Decorator is used with arguments, e.g., @functionmethod(arg1=val1, arg2=val2)
return decorator
if len(args) == 0:
return decorator
if len(args) == 1:
return decorator(args[0])
# Example usages
@customdecorator
def example1():
print("Function without call")
@customdecorator()
def example2():
print("Function without arguments")
@customdecorator(arg1="value1", arg2="value2")
def example3(arg2):
print(f"Function with arguments: {arg2}")
example1()
example2()
example3(arg2="ex2")
In your case, it will be
def myDecorator(*args, **kwargs):
def decorator(func):
def __wrapper(*func_args, **func_kwargs):
# Do something before calling the function
test_func = kwargs.get('test_func', None)
logIt = kwargs.get('logIt', None)
if logIt:
print("Calling Function: " + test_func.__name__)
result = func(*func_args, **func_kwargs)
# Do something after calling the function
return result
return __wrapper
if len(kwargs) > 0:
# Decorator is used with arguments, e.g., @functionmethod(arg1=val1, arg2=val2)
return decorator
if len(args) == 0:
return decorator
if len(args) == 1:
return decorator(args[0])
@myDecorator(logIt=False)
def someFunc():
print('Hello')
someFunc()
The caveat is:
kwargs.get(..., ...)
.Upvotes: 0
Reputation: 2613
Just want to add some usefull trick that will allow to make decorator arguments optional. It will also alows to reuse decorator and decrease nesting
import functools
def myDecorator(test_func=None,logIt=None):
if test_func is None:
return functools.partial(myDecorator, logIt=logIt)
@functools.wraps(test_func)
def f(*args, **kwargs):
if logIt==1:
print 'Logging level 1 for {}'.format(test_func.__name__)
if logIt==2:
print 'Logging level 2 for {}'.format(test_func.__name__)
return test_func(*args, **kwargs)
return f
#new decorator
myDecorator_2 = myDecorator(logIt=2)
@myDecorator(logIt=2)
def pow2(i):
return i**2
@myDecorator
def pow3(i):
return i**3
@myDecorator_2
def pow4(i):
return i**4
print pow2(2)
print pow3(2)
print pow4(2)
Upvotes: 40
Reputation: 27486
Now if you want to call a function function1
with a decorator decorator_with_arg
and in this case both the function and the decorator take arguments,
def function1(a, b):
print (a, b)
decorator_with_arg(10)(function1)(1, 2)
Upvotes: 3
Reputation: 1807
Just another way of doing decorators. I find this way the easiest to wrap my head around.
class NiceDecorator:
def __init__(self, param_foo='a', param_bar='b'):
self.param_foo = param_foo
self.param_bar = param_bar
def __call__(self, func):
def my_logic(*args, **kwargs):
# whatever logic your decorator is supposed to implement goes in here
print('pre action baz')
print(self.param_bar)
# including the call to the decorated function (if you want to do that)
result = func(*args, **kwargs)
print('post action beep')
return result
return my_logic
# usage example from here on
@NiceDecorator(param_bar='baaar')
def example():
print('example yay')
example()
Upvotes: 43
Reputation: 110203
Since you are calling the decorator like a function, it needs to return another function which is the actual decorator:
def my_decorator(param):
def actual_decorator(func):
print("Decorating function {}, with parameter {}".format(func.__name__, param))
return function_wrapper(func) # assume we defined a wrapper somewhere
return actual_decorator
The outer function will be given any arguments you pass explicitly, and should return the inner function. The inner function will be passed the function to decorate, and return the modified function.
Usually you want the decorator to change the function behavior by wrapping it in a wrapper function. Here's an example that optionally adds logging when the function is called:
def log_decorator(log_enabled):
def actual_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
if log_enabled:
print("Calling Function: " + func.__name__)
return func(*args, **kwargs)
return wrapper
return actual_decorator
The functools.wraps
call copies things like the name and docstring to the wrapper function, to make it more similar to the original function.
Example usage:
>>> @log_decorator(True)
... def f(x):
... return x+1
...
>>> f(4)
Calling Function: f
5
Upvotes: 241
Reputation: 123782
Just to provide a different viewpoint: the syntax
@expr
def func(...): #stuff
is equivalent to
def func(...): #stuff
func = expr(func)
In particular, expr
can be anything you like, as long as it evaluates to a callable. In particular particular, expr
can be a decorator factory: you give it some parameters and it gives you a decorator. So maybe a better way to understand your situation is as
dec = decorator_factory(*args)
@dec
def func(...):
which can then be shortened to
@decorator_factory(*args)
def func(...):
Of course, since it looks like decorator_factory
is a decorator, people tend to name it to reflect that. Which can be confusing when you try to follow the levels of indirection.
Upvotes: 62