[Python-ideas] Proposal for new-style decorators
Mathias Panzenböck
grosser.meister.morti at gmx.net
Thu Apr 28 07:18:35 CEST 2011
Just before I go to bed, here some alternative implementations:
Usage:
------
# for argument-less decorators:
@decorator
def my_deco(func,*func_args,**func_kwargs):
pass
# of course regular arguments can also be declared (same for all funcs below)
@my_deco
def func(*func_args,**func_kwargs):
pass
# for decorators with arguments:
# even when there are no default arguments function-call parenthesis are needed
@decorator_with_args('foo',bar='baz')
def my_deco(func,deco_args,deco_kwargs,*func_args,**func_kwargs):
pass
@my_deco(*deco_args,**deco_kwargs)
def func(*func_args,**func_kwargs):
pass
# alternative version where the decorator arguments are expanded:
# `...` is a placeholder for the arguments of the decorator in regular argument syntax.
# This way the decorator arguments can be declared inline and no deco_(kw)args or self.*
# is needed. Also decorator arguments are not decoupled from their default values this way.
@decorator_with_expanded_args
def my_deco(func,...,*func_args,**func_kwargs):
pass
@my_deco(*deco_args,**deco_kwargs)
def func(*func_args,**func_kwargs):
pass
Implementation:
---------------
from types import FunctionType, ClassType
from functools import wraps
def decorator(deco):
@wraps(deco)
def _deco(func):
@wraps(func)
def _f(*args,**kwargs):
return deco(func,*args,**kwargs)
return _f
return _deco
def decorator_with_args(*deco_default_args,**deco_default_kwargs):
def _deco_deco_deco(deco):
@wraps(deco)
def _deco_deco(*deco_args,**deco_kwargs):
if len(deco_args) < len(deco_default_args):
deco_args = deco_args+deco_default_args[len(deco_args):]
merged_deco_kwargs = dict(deco_default_kwargs)
merged_deco_kwargs.update(deco_kwargs)
del deco_kwargs
def _deco(func):
@wraps(func)
def _f(*args,**kwargs):
return deco(
func,deco_args,merged_deco_kwargs,*args,**kwargs)
return _f
return _deco
return _deco_deco
return _deco_deco_deco
def decorator_with_expanded_args(deco):
if isinstance(deco, FunctionType):
co = deco.func_code
deco_name = deco.func_name
arg_names = list(co.co_varnames[0:co.co_argcount])
elif isinstance(deco, ClassType):
co = deco.__init__.func_code
deco_name = deco.__name__
arg_names = list(co.co_varnames[1:co.co_argcount])
elif hasattr(deco, '__call__'):
co = deco.__call__.func_code
deco_name = type(deco).__name__
arg_names = list(co.co_varnames[0:co.co_argcount])
else:
raise TypeError('not a decorateable object')
if not arg_names:
raise TypeError('decorator function needs a func argument')
del co
del arg_names[0]
min_argcount = len(arg_names)
if deco.func_defaults:
min_argcount -= len(deco.func_defaults)
@wraps(deco)
def _deco_deco(*args,**kwargs):
deco_args = list(args)
n = len(deco_args)
if n < len(arg_names):
i = n - min_argcount
for arg in arg_names[n:]:
if arg in kwargs:
deco_args.append(kwargs.pop(arg))
elif i < 0:
raise TypeError(
'%s() takes at least %d positional ' +
'arguments (%d given)' %
(deco_name, min_argcount, len(deco_args)))
else:
deco_args.append(deco.func_defaults[i])
i += 1
if kwargs:
arg = kwargs.keys()[0]
if arg in arg_names:
raise TypeError(
"%s() got multiple values for keyword argument '%s'" %
(deco_name, arg))
else:
raise TypeError("%s() got an unexpected keyword argument '%s'" %
(deco_name, arg))
deco_args = tuple(deco_args)
def _deco(func):
@wraps(func)
def _f(*args,**kwargs):
return deco(func,*(deco_args+args),**kwargs)
return _f
return _deco
return _deco_deco
What do you think?
-panzi
More information about the Python-ideas
mailing list