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.
