I am working within a Python web framework that uses Python 3 type annotations for validation and dependency injection.
So I am looking for a way to generate functions with type annotations from a parameters given to the generating function:
def gen_fn(args: Dict[str, Any]) -> Callable:
def new_fn(???):
pass
return new_fn
so that
inspect.signature(gen_fn({'a': int}))
will return
<Signature (a:int)>
Is there something I cam put instead of the ???
that will do the thing I need.
I also looked at Signature.replace()
in the inspect
module, but did not find a way to attach the new signature to a new or existing function.
I am hesitant to use ast because:
The abstract syntax itself might change with each Python release
So my question is: What (if any) is a reasonable way to generate a function with Python 3 type annotation based on a dict
passed to the generating function?
Edit: while @Aran-Fey's solution answer my question correctly, it appears that my assumption was wrong. Changing the signature doesn't allow calling the new_fn
using the new signature. That is gen_fn({'a': int})(a=42)
raises a TypeError:
... `got an unexpected keyword argument 'a'.
Instead of creating a function with annotations, it's easier to create a function and then set the annotations manually.
inspect.signature
looks for the existence of a __signature__
attribute before it looks at the function's actual signature, so we can craft an appropriate inspect.Signature
object and assign it there:
params = [inspect.Parameter(param,
inspect.Parameter.POSITIONAL_OR_KEYWORD,
annotation=type_)
for param, type_ in args.items()]
new_fn.__signature__ = inspect.Signature(params)
typing.get_type_hints
does not respect __signature__
, so we should update the __annotations__
attribute as well:
new_fn.__annotations__ = args
Putting them both together:
def gen_fn(args: Dict[str, Any]) -> Callable:
def new_fn():
pass
params = [inspect.Parameter(param,
inspect.Parameter.POSITIONAL_OR_KEYWORD,
annotation=type_)
for param, type_ in args.items()]
new_fn.__signature__ = inspect.Signature(params)
new_fn.__annotations__ = args
return new_fn
print(inspect.signature(gen_fn({'a': int}))) # (a:int)
print(get_type_hints(gen_fn({'a': int}))) # {'a': <class 'int'>}
Note that this doesn't make your function callable with these arguments; all of this is just smoke and mirrors that makes the function look like it has those parameters and annotations. Implementing the function is a separate issue.
You can define the function with varargs to aggregate all the arguments into a tuple and a dict:
def new_fn(*args, **kwargs):
...
But that still leaves you with the problem of implementing the function body. You haven't said what the function should do when it's called, so I can't help you with that. You can look at this question for some pointers.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With