I want my Python decorators to have optional arguments and not be called when not necessary.
The accepted answer in here doesn't accept named arguments, and I don't want to add boilerplate code inside decorators, so I came up with an alternative decorator:
import inspect
def decorator_defaults(**defined_defaults):
def decorator(f):
args_names = inspect.getargspec(f)[0]
def wrapper(*new_args, **new_kwargs):
defaults = dict(defined_defaults, **new_kwargs)
if len(new_args) == 0:
return f(**defaults)
elif len(new_args) == 1 and callable(new_args[0]):
return f(**defaults)(new_args[0])
else:
too_many_args = False
if len(new_args) > len(args_names):
too_many_args = True
else:
for i in range(len(new_args)):
arg = new_args[i]
arg_name = args_names[i]
defaults[arg_name] = arg
if len(defaults) > len(args_names):
too_many_args = True
if not too_many_args:
final_defaults = []
for name in args_names:
final_defaults.append(defaults[name])
return f(*final_defaults)
if too_many_args:
raise TypeError("{0}() takes {1} argument(s) "
"but {2} were given".
format(f.__name__,
len(args_names),
len(defaults)))
return wrapper
return decorator
Two sample decorators:
from functools import wraps
@decorator_defaults(start_val="-=[", end_val="]=-")
def my_text_decorator(start_val, end_val):
def decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
return "".join([f.__name__, ' ', start_val,
f(*args, **kwargs), end_val])
return wrapper
return decorator
@decorator_defaults(end_val="]=-")
def my_text_decorator2(start_val, end_val):
def decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
return "".join([f.__name__, ' ', start_val,
f(*args, **kwargs), end_val])
return wrapper
return decorator
And usage of sample text decorators:
@my_text_decorator
def func1a(value):
return value
@my_text_decorator()
def func2a(value):
return value
@my_text_decorator2("-=[")
def func2b(value):
return value
@my_text_decorator(end_val=" ...")
def func3a(value):
return value
@my_text_decorator2("-=[", end_val=" ...")
def func3b(value):
return value
@my_text_decorator("|> ", " <|")
def func4a(value):
return value
@my_text_decorator2("|> ", " <|")
def func4b(value):
return value
@my_text_decorator(end_val=" ...", start_val="|> ")
def func5a(value):
return value
@my_text_decorator2("|> ", end_val=" ...")
def func5b(value):
return value
print(func1a('My sample text')) # func1a -=[My sample text]=-
print(func2a('My sample text')) # func2a -=[My sample text]=-
print(func2b('My sample text')) # func2b -=[My sample text]=-
print(func3a('My sample text')) # func3a -=[My sample text ...
print(func3b('My sample text')) # func3b -=[My sample text ...
print(func4a('My sample text')) # func4a |> My sample text <|
print(func4b('My sample text')) # func4b |> My sample text <|
print(func5a('My sample text')) # func5a |> My sample text ...
print(func5b('My sample text')) # func5b |> My sample text ...
decorator_defaults
works, but I believe it could be written better.
I'm not that experienced in Python, so I would like to hear some ideas/comments on how to improve it.