Something of the following sort. Imagine this case:
def some_function(a, b):
return a b
some_magical_workaround({"a": 1, "b": 2, "c": 3}) # returns 3
I can't modify some_function
to add a **kwargs
parameter. How could I create a wrapper function some_magical_workaround
which calls some_function
as shown?
Also, some_magical_workaround
may be used with other functions, and I don't know beforehand what args are defined in the functions being used.
CodePudding user response:
So, you cannot do this in general if the function isn't written in Python (e.g. many built-ins, functions from third-party libraries written as extensions in C) but you can use the inpsect
module to introspect the signature. Here is a quick-and-dirty proof-of-concept, I haven't really considered edge-cases, but this should get you going:
import inspect
def bind_exact_args(func, kwargs):
sig = inspect.signature(func) # will fail with functions not written in Python, e.g. many built-ins
common_keys = sig.parameters.keys() & kwargs.keys()
return func(**{k:kwargs[k] for k in common_keys})
def some_function(a, b):
return a b
So, a demonstration:
>>> import inspect
>>>
>>> def bind_exact_args(func, kwargs):
... sig = inspect.signature(func) # will fail with functions not written in Python, e.g. many built-ins
... return func(**{k:kwargs[k] for k in sig.parameters.keys() & kwargs.keys()})
...
>>> def some_function(a, b):
... return a b
...
>>> bind_exact_args(some_function, {"a": 1, "b": 2, "c": 3})
3
But note how it can fail with built-ins:
>>> bind_exact_args(max, {"a": 1, "b": 2, "c": 3})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in bind_exact_args
File "/usr/local/Cellar/[email protected]/3.9.13_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/inspect.py", line 3113, in signature
return Signature.from_callable(obj, follow_wrapped=follow_wrapped)
File "/usr/local/Cellar/[email protected]/3.9.13_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/inspect.py", line 2862, in from_callable
return _signature_from_callable(obj, sigcls=cls,
File "/usr/local/Cellar/[email protected]/3.9.13_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/inspect.py", line 2329, in _signature_from_callable
return _signature_from_builtin(sigcls, obj,
File "/usr/local/Cellar/[email protected]/3.9.13_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/inspect.py", line 2147, in _signature_from_builtin
raise ValueError("no signature found for builtin {!r}".format(func))
ValueError: no signature found for builtin <built-in function max>
As noted in @JamieDoornbos answer, another example that will not work is a function with positional-only paramters:
E.g.:
def some_function(a, b, /, c):
return a b c
Although, you can introspect this:
>>> def some_function(a, b, /, c):
... return a b c
...
>>> sig = inspect.signature(some_function)
>>> sig.parameters['a'].kind
<_ParameterKind.POSITIONAL_ONLY: 0>
>>> sig.parameters['b'].kind
<_ParameterKind.POSITIONAL_ONLY: 0>
>>> sig.parameters['c'].kind
<_ParameterKind.POSITIONAL_OR_KEYWORD: 1>
If you need to handle this case, it is certainly possible to, but I leave that as an exercise to the reader :)
CodePudding user response:
You can use the inspect
module to find out the argument names for the original function and filter your dictionary:
import inspect
def some_function(a, b):
return a b
def some_magical_workaround(d):
args = inspect.getfullargspec(some_function)[0]
return some_function(**{k: v for k, v in d.items() if k in args})
print(some_magical_workaround({"a": 1, "b": 2, "c": 3}))
This will print:
3
It can even be made more general by making the function itself an argument:
def some_magical_workaround(func, d):
args = inspect.getfullargspec(func)[0]
...
print(some_magical_workaround(some_function, {"a": 1, "b": 2, "c": 3}))
CodePudding user response:
If you want to pass the entire dict to a wrapper function, you can do so, read the keys internally, and pass them along too
def wrapper_for_some_function(source_dict):
# two possible choices to get the keys from the dict
a = source_dict["a"] # indexing: exactly a or KeyError
b = source_dict.get("b", 7) # .get method: 7 if there's no b
# now pass the values from the dict to the wrapped function
return some_function(a, b)
original answer components
If instead, you can unpack the dict, when you define your function, you can add a **kwargs
argument to eat up unknown args
def some_function(a, b, **kwargs):
return a b
>>> def some_function(a, b, **kwargs):
... return a b
...
>>> d = {"a":1,"b":2,"c":3}
>>> some_function(**d) # dictionary unpacking
3
CodePudding user response:
For basic use cases, you can do something like this:
import inspect
def some_magical_workaround(fn, params):
return fn(**{
name: value for name, value in params.items()
if name in set(inspect.getfullargspec(fn)[0])
})
some_magical_workaround(some_function, {"a":1,"b":2,"c":3})
However, be warned that calling functions this way circumvents the explicit coupling of parameters, which is designed to expose errors earlier in the development process.
And there are some constraints on the values of fn
that will work as expected. some_function
is fine, but here's an alternative that won't work because the parameters are not allowed to have names:
>>> def some_function2(a, b, /):
... return a b
...
>>> apply_by_name(some_function2, {"a":1,"b":2,"c":3})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in apply_by_name
TypeError: some_function() got some positional-only arguments passed as keyword arguments: 'a, b'