Programmatically Create Function Specification
Solution 1:
You can use exec
to construct the function object from a string containing Python code:
defmake_fun(parameters):
exec("def f_make_fun({}): pass".format(', '.join(parameters)))
returnlocals()['f_make_fun']
Example:
>>> f = make_fun(['a', 'b'])
>>> import inspect
>>> print(inspect.signature(f).parameters)
OrderedDict([('a', <Parameter at 0x1024297e0'a'>), ('b', <Parameter at 0x102429948'b'>)])
If you want more functionality (e.g., default argument values), it's a matter of adapting the string that contains the code and having it represent the desired function signature.
Disclaimer: as pointed out below it's important that you verify the contents of parameters
and that the resulting Python code string is safe to pass to exec
. You should construct parameters
yourself or put restrictions in place to prevent the user from constructing a malicious value for parameters
.
Solution 2:
One of the possible solutions using a class:
defmake_fun(args_list):
args_list = args_list[:]
classMyFunc(object):
def__call__(self, *args, **kwargs):
iflen(args) > len(args_list):
raise ValueError('Too many arguments passed.')
# At this point all positional arguments are fine.for arg in args_list[len(args):]:
if arg notin kwargs:
raise ValueError('Missing value for argument {}.'.format(arg))
# At this point, all arguments have been passed either as# positional or keyword.iflen(args_list) - len(args) != len(kwargs):
raise ValueError('Too many arguments passed.')
for arg in args:
print(arg)
for arg in args_list[len(args):]:
print(kwargs[arg])
return MyFunc()
functionA = make_fun(['paramA', 'paramB'])
functionB = make_fun(['arg1', 'arg2', 'arg3'])
functionA(3, paramB=1) # Workstry:
functionA(3, 2, 1) # Failsexcept ValueError as e:
print(e)
try:
functionB(0) # Failsexcept ValueError as e:
print(e)
try:
functionB(arg1=1, arg2=2, arg3=3, paramC=1) # Failsexcept ValueError as e:
print(e)
Solution 3:
Here's another way to do it using functools.wrap
, which preserves signature and docstring, at least in python 3. The trick is to create the signature and documentation in dummy functions which never get called. Here are a couple of examples.
Basic example
import functools
defwrapper(f):
@functools.wraps(f)deftemplate(common_exposed_arg, *other_args, common_exposed_kwarg=None, **other_kwargs):
print("\ninside template.")
print("common_exposed_arg: ", common_exposed_arg, ", common_exposed_kwarg: ", common_exposed_kwarg)
print("other_args: ", other_args, ", other_kwargs: ", other_kwargs)
return template
@wrapperdefexposed_func_1(common_exposed_arg, other_exposed_arg, common_exposed_kwarg=None):
"""exposed_func_1 docstring: this dummy function exposes the right signature"""print("this won't get printed")
@wrapperdefexposed_func_2(common_exposed_arg, common_exposed_kwarg=None, other_exposed_kwarg=None):
"""exposed_func_2 docstring"""pass
exposed_func_1(10, -1, common_exposed_kwarg='one')
exposed_func_2(20, common_exposed_kwarg='two', other_exposed_kwarg='done')
print("\n" + exposed_func_1.__name__)
print(exposed_func_1.__doc__)
And the result is:
>> inside template.
>>common_exposed_arg:10 , common_exposed_kwarg: one
>>other_args: (-1,) , other_kwargs: {}
>>>> inside template.
>>common_exposed_arg:20 , common_exposed_kwarg: two
>>other_args: () , other_kwargs: {'other_exposed_kwarg': 'done'}
>>>> exposed_func_1
>> exposed_func_1 docstring: this dummy function exposes the right signature
Calling inspect.signature(exposed_func_1).parameters
returns the desired signature. Using inspect.getfullargspec(exposed_func_1)
, however, still returns the signature of template
. At least if you put any arguments common to all functions you want to make in the definition of template
, those will appear.
If this is a bad idea for some reason, please let me know!
More complicated example
And you can get much more complicated than this, by layering in more wrappers and defining more distinct behaviors in an inner function:
import functools
defwrapper(inner_func, outer_arg, outer_kwarg=None):
defwrapped_func(f):
@functools.wraps(f)deftemplate(common_exposed_arg, *other_args, common_exposed_kwarg=None, **other_kwargs):
print("\nstart of template.")
print("outer_arg: ", outer_arg, " outer_kwarg: ", outer_kwarg)
inner_arg = outer_arg * 10 + common_exposed_arg
inner_func(inner_arg, *other_args, common_exposed_kwarg=common_exposed_kwarg, **other_kwargs)
print("template done")
return template
return wrapped_func
# Build two examples.definner_fcn_1(hidden_arg, exposed_arg, common_exposed_kwarg=None):
print("inner_fcn, hidden_arg: ", hidden_arg, ", exposed_arg: ", exposed_arg, ", common_exposed_kwarg: ", common_exposed_kwarg)
definner_fcn_2(hidden_arg, common_exposed_kwarg=None, other_exposed_kwarg=None):
print("inner_fcn_2, hidden_arg: ", hidden_arg, ", common_exposed_kwarg: ", common_exposed_kwarg, ", other_exposed_kwarg: ", other_exposed_kwarg)
@wrapper(inner_fcn_1, 1)defexposed_function_1(common_exposed_arg, other_exposed_arg, common_exposed_kwarg=None):
"""exposed_function_1 docstring: this dummy function exposes the right signature """print("this won't get printed")
@wrapper(inner_fcn_2, 2, outer_kwarg="outer")defexposed_function_2(common_exposed_arg, common_exposed_kwarg=None, other_exposed_kwarg=None):
""" exposed_2 doc """pass
It's a bit verbose, but the point is that there is a lot of flexibility in where the dynamic inputs from you (the programmer) come in when using this to create functions, and so with where the exposed inputs (from user of the function) get used.
Post a Comment for "Programmatically Create Function Specification"