
I thought about this problem for I wile and I came up with this @decorator: Usage: ------
@decorator def my_deco(func,func_args,func_kwargs,deco_args...): pass
@my_deco(*deco_args,**deco_kwargs) def func(*func_args,**func_kwargs): pass
Or a more specific example:
@decorator def deco(func,func_args,func_kwargs,a,b=12,**kwargs): return (func(*func_args,**func_kwargs),func_args,func_kwargs,a,b,kwargs)
@deco(1,f=12) def foo(x,y,z=2,*args): return (x,y,z,args)
foo(5,6) ((5, 6, 2, ()), (5, 6, 2), {}, 1, 12, {'f': 12})
This fully supports *args and **kwargs besides regular arguments for the decorator and the decorated function. By that I mean func_args already contains the filled in default values of func as well as any regular arguments that where passed as keyword argument and argument passing errors are handled (e.g. passing an argument as positional and keyword argument). Error handling example:
@deco(1,2,3) def bar(x,y,z=2,*args): return (x,y,z,args)
Traceback (most recent call last): File "<pyshell#114>", line 1, in <module> @deco(1,2,3) File "<pyshell#96>", line 22, in _deco_deco deco_args, deco_kwargs = apply_deco_args(*deco_args, **deco_kwargs) TypeError: deco() takes at most 2 arguments (3 given) Or:
foo(5,6,y=33)
Traceback (most recent call last): File "<pyshell#112>", line 1, in <module> foo(5,6,y=33) File "<pyshell#96>", line 27, in _f func_args, func_kwargs = apply_func_args(*func_args, **func_kwargs) TypeError: foo() got multiple values for keyword argument 'y' Of course that always needs function call parenthesis on the decorator, even if the decorator does not take any arguments. Maybe it could be extended that in this case a more simple decorator mechanism is used. A decorator-decorator for decorators without arguments would be very simple (see end of mail). Implementation: --------------- from types import FunctionType, ClassType from functools import wraps def inspect_callable(func): """-> (arg_names, co_flags, func_defaults, func_name)""" return _inspect_callable(func,set()) def _inspect_callable(func,visited): if func in visited: raise TypeError("'%s' object is not callable" % type(func).__name__) visited.add(func) if isinstance(func, FunctionType): co = func.func_code func_name = func.__name__ arg_names = list(co.co_varnames[0:co.co_argcount]) defaults = func.func_defaults flags = co.co_flags elif isinstance(func, ClassType): func_name = func.__name__ arg_names, flags, defaults, member_name = _inspect_callable(func.__init__,visited) if arg_names: del arg_names[0] elif hasattr(func, '__call__'): func_name = '<%s object at 0x%x>' % (type(func).__name__, id(func)) arg_names, flags, defaults, member_name = _inspect_callable(func.__call__,visited) else: raise TypeError("'%s' object is not callable" % type(func).__name__) return arg_names, flags, defaults, func_name FUNC_ARGS = 0x04 FUNC_KWARGS = 0x08 FUNC_GEN = 0x20 # this function should probably be reimplemented in C: def args_applyer(arg_names,flags=0,defaults=None,func_name=None): """-> f(args..., [*varargs], [**kwargs]) -> ((args...)+varargs, kwargs)""" all_args = list(arg_names) if arg_names: body = ['(',','.join(arg_names),')'] else: body = [] if flags & FUNC_ARGS: args_name = '_args' i = 0 while args_name in arg_names: args_name = '_args'+i i += 1 all_args.append('*'+args_name) if arg_names: body.append('+') body.append(args_name) elif not arg_names: body.append('()') body.append(',') if flags & FUNC_KWARGS: kwargs_name = '_kwargs' i = 0 while kwargs_name in arg_names: kwargs_name = '_kwargs'+i i += 1 all_args.append('**'+kwargs_name) body.append(kwargs_name) else: body.append('{}') if func_name: apply_args = named_lambda(func_name,all_args,''.join(body)) else: apply_args = eval('lambda %s: (%s)' % (','.join(all_args), ''.join(body))) if defaults: apply_args.func_defaults = defaults return apply_args def named_lambda(name,args,body): code = 'def _named_lambda():\n\tdef %s(%s):\n\t\treturn %s\n\treturn %s' % ( name, ','.join(args), body, name) del name, args, body exec(code) return _named_lambda() # begin helper functions (not used by this module but might be handy for decorator developers) def args_applyer_for(func): return args_applyer(*inspect_callable(func)) def apply_args(args,kwargs,arg_names,flags=0,defaults=None,func_name=None): return args_applyer(arg_names,flags,defaults,func_name)(*args,**kwargs) def apply_args_for(func,args,kwargs): return args_applyer(*inspect_callable(func))(*args,**kwargs) # end helper functions def decorator(deco): """deco(func,func_args,func_kwargs,deco_args...) @decorator def my_deco(func,func_args,func_kwargs,deco_args...): pass @my_deco(*deco_args,**deco_kwargs) def func(*func_args,**func_kwargs): pass """ arg_names, flags, defaults, deco_name = inspect_callable(deco) if flags & FUNC_ARGS == 0: if len(arg_names) < 3: raise TypeError('decorator functions need at least 3 ' + 'arguments (func, func_args, func_kwargs)') del arg_names[0:3] apply_deco_args = args_applyer(arg_names,flags,defaults,deco_name) del flags, defaults @wraps(deco) def _deco_deco(*deco_args,**deco_kwargs): deco_args, deco_kwargs = apply_deco_args(*deco_args, **deco_kwargs) def _deco(func): apply_func_args = args_applyer(*inspect_callable(func)) @wraps(func) def _f(*func_args,**func_kwargs): func_args, func_kwargs = apply_func_args(*func_args, **func_kwargs) return deco(func,func_args,func_kwargs,*deco_args,**deco_kwargs) return _f return _deco return _deco_deco def simple_decorator(deco): """deco(func,func_args,func_kwargs) @simple_decorator def my_deco(func,func_args,func_kwargs): pass @my_deco def func(*func_args,**func_kwargs): pass """ @wraps(deco) def _deco(func): apply_func_args = args_applyer(*inspect_callable(func)) @wraps(func) def _f(*args,**kwargs): return deco(func,*apply_func_args(*args,**kwargs)) return _f return _deco