I want to create a decorator that combines two functions and combines the parameters from their signatures.
The interface I want:
def f(a, b, c, d, e, f, g, h, i, j, k, l, m, n):
# I am using many parameters to explain the need of not
# needing to type the arguments again.
return a * b * c * d * e * f * g * h * i * j * k * l * m * n
@combines(f)
def g(o, p, *args, **kwargs):
return (o + p) * f(*args, **kwargs)
This should essentially result in:
def g(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p):
return (o + p) * (a * b * c * d * e * f * g
* h * i * j * k * l * m * n)
The reason I want this is because I don't really know the arguments of function f
(I know them, but I don't want to type them again in order to make it general.)
I am not sure if I have to call g
with *args
and **kwargs
, but I think this will be necessary.
This is how far I got:
import functools
import inspect
def combines(old_func):
old_sig = inspect.signature(old_func)
old_parameters = old_sig.parameters
def insert_in_signature(new_func):
new_parameters = inspect.signature(new_func).parameters
for new_parameter in new_parameters:
if new_parameter in old_parameters.keys():
raise TypeError('`{}` argument already defined'.format(new_parameter))
@functools.wraps(new_func)
def wrapper(*args, **kwargs):
return old_func(*args, **kwargs) * new_func(*args, **kwargs)
parms = list(old_parameters.values())
for arg, par in new_parameters.items():
if par.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD:
parms.append(inspect.Parameter(arg, par.kind))
wrapper.__signature__ = old_sig.replace(parameters=parms)
return wrapper
return insert_in_signature
def f(a, b, c, d, e, f, g, h, i, j, k, l, m, n):
return a * b * c * d * e * f * g * h * i * j * k * l * m * n
@combines(f)
def g(o, p, *args, **kwargs):
return (o + p) * f(*args, **kwargs)
This results in the desired calling signature of g
, but it does not work.
EDIT because the error message was asked
For example: g(1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-23-2775f64e1b3e> in <module>()
----> 1 g(1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1)
<ipython-input-18-3a843320e4e3> in wrapper(*args, **kwargs)
13 @functools.wraps(new_func)
14 def wrapper(*args, **kwargs):
---> 15 return old_func(*args, **kwargs) * new_func(*args, **kwargs)
16
17 parms = list(old_parameters.values())
TypeError: f() takes 14 positional arguments but 16 were given
if I then follow the error message and give 14 arguments with g(1,1,1,1,1,1,1,1,1,1,1,1,1,1)
:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-24-052802b037a4> in <module>()
----> 1 g(1,1,1,1,1,1,1,1,1,1,1,1,1,1)
<ipython-input-18-3a843320e4e3> in wrapper(*args, **kwargs)
13 @functools.wraps(new_func)
14 def wrapper(*args, **kwargs):
---> 15 return old_func(*args, **kwargs) * new_func(*args, **kwargs)
16
17 parms = list(old_parameters.values())
<ipython-input-18-3a843320e4e3> in g(o, p, *args, **kwargs)
29 @combines(f)
30 def g(o, p, *args, **kwargs):
---> 31 return (o + p) * f(*args, **kwargs)
TypeError: f() missing 2 required positional arguments: 'm' and 'n'
So clearly my implementation is not really working.
Your problem is that you call the f
function twice with different parameters, once inside the original g
with only its expected parameters and once inside the wrapper with all the parameters.
You must choose one, my advice is to remove its call from the original g
I have slightly changed your code, but at least my version works in Python 3.5:
f
and for g
Here is the code:
def combine(ext):
ext_params = inspect.signature(ext).parameters
def wrapper(inn):
inn_params = inspect.signature(inn).parameters
for k in inn_params.keys():
if k in ext_params.keys():
raise TypeError('`{}` argument already defined'.format(
k))
all_params = list(ext_params.values()) + \
list(inn_params.values())
# computes the signature for the wrapped function
sig = inspect.signature(inn).replace(parameters = all_params)
def wrapped(*args, **kwargs):
# signature bind magically processes positional and keyword arguments
act_args = sig.bind(*args, **kwargs).args
ext_args = act_args[:len(ext_params.keys())] # for external function
inn_args = act_args[len(ext_params.keys()):] # for inner function
return ext(*ext_args) * inn(*inn_args)
w = functools.update_wrapper(wrapped, inn) # configure the wrapper function
w.__signature__ = sig # and set its signature
return w
return wrapper
I can now write:
>>> @combine(f)
def g(o,p):
return o+p
>>> help(g)
Help on function g in module __main__:
g(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p)
>>> g(1,1,1,1,1,1,1,1,1,1,1,1,1,p=1, o=1, n=1)
2