Reputation: 1035
I have a YAML script where the users can define some parameters. They can also state in the YAML file a custom function they want to apply. For example:
processes:
args1: 10
args2: 5
funct: np.sum
This YAML will be passed to a function like this:
def custom_func(args1, args2, funct):
return funct(args1, args2)
With the YAML example above, I expect custom_func()
to execute np.sum(10,5)
. How can I make the data in YAML file to be parsed as Callable? eval()
probably does the job but it might have security issue. Is there any proper way? Thanks!
Upvotes: 3
Views: 11593
Reputation: 1925
Yes, eval
and exec
are both bad practices. Good that you know that :D
You can use dictionaries to do this.
What you can do is define a dictionary and make the key the funct
value in YAML. The value will be an uncalled function. This is similar to a variable.
funct_parser = {
"np.sum": np.sum # Notice how this doesn't have ()
}
You can then use funct
against the dictionary to call the proper method. Something like the following:
def custom_func(args1, args2, funct):
my_func = funct_parser[str(funct)] # This will cause my_func to be a copy of `np.sum`.
return my_func(args1, args2) # Call my_func
BTW, you don't really have to do custom_func
on multiple lines, I just did so to explain it better.
Alternative choice:
If you don't feel like hardcoding every single part in a dictionary, you can use getattr()
.
# This code assumes you have numpy imported as np (You can always change it)
def custom_func(args1, args2, funct):
funct = funct.split("np.")[0] # Will work for np. You can always change to other modules when needed.
return getattr(np, funct)
Upvotes: 0
Reputation: 12214
You can also load the function given its Python dotted path.
However, this only gets the function and calls it, it does not try to figure out calling signature, which is np.sum([5,10])
, not np.sum(10,5)
from yaml import safe_load as yload
from importlib import import_module
def load_func(dotpath : str):
""" load function in module. function is right-most segment """
module_, func = dotpath.rsplit(".", maxsplit=1)
m = import_module(module_)
return getattr(m, func)
yaml = """
processes:
args1: 10
args2: 5
#gotta give real package name, not `np`
funct: numpy.sum
"""
di = yload(yaml)
pr = di["processes"]
func = load_func(pr["funct"])
array = [pr["args1"], pr["args2"]]
print (f"{func=} {func(array)=}")
func=<function sum at 0x106645e50> func(array)=15
Upvotes: 6