【问题标题】:Python metaprogramming: generate a function signature with type annotationPython 元编程:使用类型注释生成函数签名
【发布时间】:2018-10-27 08:00:04
【问题描述】:

我正在使用 Python 3 类型注释进行验证和依赖注入的 Python Web 框架。

所以我正在寻找一种方法来从给定生成函数的参数中生成带有类型注释的函数:

def gen_fn(args: Dict[str, Any]) -> Callable:
    def new_fn(???):
        pass
    return new_fn

这样

inspect.signature(gen_fn({'a': int}))

会回来

<Signature (a:int)>

有什么我可以代替 ??? 的东西来做我需要的事情。

我还查看了inspect 模块中的Signature.replace(),但没有找到将新签名附加到新函数或现有函数的方法。

我对使用ast 犹豫不决,因为:

抽象语法本身可能会随着每个 Python 版本而改变

所以我的问题是:基于传递给生成函数的dict,生成具有 Python 3 类型注释的函数的合理方法是什么(如果有)?


编辑:虽然@Aran-Feysolution 正确回答了我的问题,但看来我的假设是错误的。更改签名不允许使用新签名调用new_fn。那就是gen_fn({'a': int})(a=42) 引发了TypeError: ...`得到了一个意外的关键字参数'a'。

【问题讨论】:

  • 我想实现这一点的一种方法是构建一个字符串和eval() 整个字符串来获取函数定义。但我认为这不是最好的方法。
  • MonkeyType 可以为某些函数生成类型注解,但需要在运行时检查类型。

标签: python metaprogramming type-hinting


【解决方案1】:

与创建带有注释的函数相比,创建函数然后手动设置注释更容易。

  • inspect.signature 在查看函数的实际签名之前会查找 __signature__ 属性的存在,因此我们可以制作一个合适的 inspect.Signature 对象并将其分配到那里:

    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尊重__signature__,所以我们也应该更新__annotations__属性:

    new_fn.__annotations__ = args
    

把它们放在一起:

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'>}

请注意,这不会使您的函数可调用这些参数;所有这一切都只是使函数看起来看起来像它具有这些参数和注释的烟雾和镜子。 实现函数是一个单独的问题。

您可以使用varargs 定义函数,将所有参数聚合成一个元组和一个字典:

def new_fn(*args, **kwargs):
    ...

但这仍然给你留下了实现函数体的问题。你还没有说函数在被调用时应该做什么,所以我无法帮助你。您可以查看this question 以获得一些指示。

【讨论】:

  • 这似乎是正确的,但不足以满足我的需求。请参阅Edit OP。
  • @ChenLevy 是的,实现该功能是一个单独的问题。如果将函数定义为def new_fn(*args, **kwargs):,则可以使函数接受任意参数,但您仍然必须实现函数的主体。你还没有指定函数在被调用时应该做什么,所以我不能给你太多建议。根据具体情况,您甚至可能不得不在运行时重新编写代码,然后 execing 它。
  • 你成就了我的一天,先生!
猜你喜欢
  • 1970-01-01
  • 2020-08-27
  • 1970-01-01
  • 1970-01-01
  • 2017-06-16
  • 2021-11-27
  • 2016-11-20
  • 2017-04-07
  • 2020-08-10
相关资源
最近更新 更多