[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