import inspect
from typing import Callable, List
Disclaimer: Lots of dumb things ahead (possibly).
I’m not a professional coder, so please don’t take my code as a reference. I’m just a beginner who is trying to learn and improve. I’m open to suggestions and corrections.
------------------
I was wondering if one function is going to delegate to multiple funcions …
Can we use multiple @delegates?
def my_function1(a, b=2, c=3, **kwargs):
print(a, b, c)
def my_function2(x, y=5, z=6, c=7, **kwargs):
print(x, y, z, c)
@delegates(my_function1)
@delegates(my_function2)
def caller_v1(**kwargs):
my_function1(**kwargs)
my_function2(**kwargs)
# print the caller_v1 signature
print(inspect.signature(caller_v1))
(*, y=5, z=6, c=7)
So the answer is NO. Only one (the last) @delegates is used.
------------------
# But what if we pass to delegates a lambda with all the parameters?
@delegates(lambda a, b=2, y=5, z=6, c=7, **kwargs: None)
def caller_v2(**kwargs):
pass
# print the caller_v2 signature
print(inspect.signature(caller_v2))
(*, b=2, y=5, z=6, c=7)
ok, it works
This function will return a lambda with the combination of parameters.
If there is a conflict it will say it, and use the value of the last one provided.
import inspect
from typing import Callable, Dict, Any
def create_lambda_with_defaults(*funcs: Callable) -> Callable:
"""
Given one or more functions, find their arguments with default values
and return a lambda that unites them all.
If there is a conflict (same argument with different default values), print a warning.
"""
defaults = {}
conflicts = {}
for func in funcs:
sig = inspect.signature(func)
for name, param in sig.parameters.items():
if param.default is not param.empty:
if name in defaults and defaults[name] != param.default:
conflicts[name] = (defaults[name], param.default)
defaults[name] = param.default
if conflicts:
for arg, (val1, val2) in conflicts.items():
print(f"Warning: Conflict for argument '{arg}' with default values {val1} and {val2}")
# Create the lambda with explicit arguments
arg_list = ', '.join(f'{k}={v!r}' for k, v in defaults.items())
lambda_code = f'lambda {arg_list}: {defaults}'
combined_lambda = eval(lambda_code)
return combined_lambda
# Example usage:
def func1(a=1, b=2):
pass
def func2(a=10, y=20):
pass
combined_lambda = create_lambda_with_defaults(func1, func2)
print(combined_lambda()) # Output: {'a': 10, 'b': 2, 'y': 20}
print(combined_lambda(a=100, y=200)) # Output: {'a': 100, 'b': 2, 'y': 200}
# Print the signature of the lambda
print(inspect.signature(combined_lambda))
Warning: Conflict for argument 'a' with default values 1 and 10
{'a': 10, 'b': 2, 'y': 20}
{'a': 10, 'b': 2, 'y': 20}
(a=10, b=2, y=20)
Now, putting all together, we can use this function to create a lambda with the parameters we want to use in the function we are delegating to.
# You can do it separatedly
_to_call = create_lambda_with_defaults(my_function1, my_function2)
# print the signature of the lambda
print(inspect.signature(_to_call))
Warning: Conflict for argument 'c' with default values 3 and 7
(b=2, c=7, y=5, z=6)
# or all at once
@delegates(create_lambda_with_defaults(my_function1, my_function2))
def caller_v3(**kwargs):
my_function1(**kwargs)
my_function2(**kwargs)
pass
# print the caller_v3 signature
print(inspect.signature(caller_v3))
Warning: Conflict for argument 'c' with default values 3 and 7
(*, b=2, c=7, y=5, z=6)
------------------
This call doesn’t fail because both my_function1
and my_function2
accept **kwargs
But that may not be the case.
caller_v3(a=88,x=99)
88 2 3
99 5 6 7
def my_function1(a, b=2, c=3): # removed **kwargs
print(a, b, c)
def my_function2(x, y=5, z=6, c=7): # removed **kwargs
print(x, y, z, c)
@delegates(create_lambda_with_defaults(my_function1, my_function2))
def caller_v4(**kwargs):
my_function1(**kwargs)
my_function2(**kwargs)
pass
# print the caller_v4 signature
print(inspect.signature(caller_v4))
caller_v4(a=88,x=99)
Warning: Conflict for argument 'c' with default values 3 and 7
(*, b=2, c=7, y=5, z=6)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[1365], line 21
17 # print the caller_v4 signature
18 print(inspect.signature(caller_v4))
---> 21 caller_v4(a=88,x=99)
Cell In[1365], line 11, in caller_v4(**kwargs)
9 @delegates(create_lambda_with_defaults(my_function1, my_function2))
10 def caller_v4(**kwargs):
---> 11 my_function1(**kwargs)
12 my_function2(**kwargs)
13 pass
TypeError: my_function1() got an unexpected keyword argument 'x'
------------------
Maybe this is overengineering, but we can filter what goes to the delegated function depending on the parameters it accepts.
import inspect
def filtered_send(data: dict, recipient_function: callable) -> dict:
"""
Filters the data dictionary to only include keys that are present in the parameter names of the recipient function,
unless the recipient function accepts **kwargs.
Args:
data (dict): The dictionary containing the data to be filtered.
recipient_function (function): The function that will receive the filtered data.
Returns:
dict: The filtered data dictionary.
"""
# Get the signature of the recipient function
signature = inspect.signature(recipient_function)
# Check if the recipient function accepts **kwargs
if any(param.kind == param.VAR_KEYWORD for param in signature.parameters.values()):
return data
# Extract the parameter names
parameter_names = set(signature.parameters.keys())
# Filter the data dictionary to only include keys that are in the parameter names
filtered_data = {key: value for key,
value in data.items() if key in parameter_names}
return filtered_data
def my_function1(a, b=2, c=3): # removed **kwargs
print(a, b, c)
def my_function2(x, y=5, z=6, c=7): # removed **kwargs
print(x, y, z, c)
@delegates(create_lambda_with_defaults(my_function1, my_function2))
def caller_v5(**kwargs):
d1=filtered_send(kwargs,my_function1)
my_function1(**d1)
d2=filtered_send(kwargs,my_function2)
my_function2(**d2)
pass
# print the caller_v5 signature
print(inspect.signature(caller_v5))
caller_v5(a=88, x=99)
Warning: Conflict for argument 'c' with default values 3 and 7
(*, b=2, c=7, y=5, z=6)
88 2 3
99 5 6 7
# ==========================