首页 > 解决方案 > 如何在 python 函数范围内获取类型注释?

问题描述

例如:

def test():
    a: int
    b: str
    print(__annotations__)
test()

此函数调用引发NameError: name '__annotations__' is not defined错误。

我想要的是在函数中获取类型注释test,比如在全局范围或类范围内返回的注释字典。

它有什么方法可以做到这一点?

如果不可能,为什么存在这种语法?

标签: pythonpython-3.xannotationstype-hinting

解决方案


在函数中,局部变量的注释不会保留,因此无法在函数中访问。只有模块和类级别的变量注释才会导致__annotations__附加对象。

PEP 526 规范

注释局部变量将导致解释器将其视为局部变量,即使它从未被分配给。不会评估局部变量的注释[.]

[...]

此外,在模块或类级别,如果被注释的项目是一个简单的名称,那么它和注释将存储在该__annotations__模块或类的属性中[.]

仅当定义了实际的模块级注释__annotations__时才设置全局;数据模型声明它是可选的:

模块
[...]预定义(可写)属性:[...] ; __annotations__(可选)是包含在模块主体执行期间收集的变量注释的字典;[...]

定义后,您可以从模块内的函数或通过globals()函数访问它。

如果您在语句内的函数中尝试此class操作,则要知道类主体名称空间不属于嵌套函数的范围

类块中定义的名称范围仅限于类块;它没有扩展到方法的代码块——这包括理解和生成器表达式,因为它们是使用函数范围实现的。

相反,您可以通过对类的引用来访问类命名空间。您可以通过使用类全局名称或内部绑定方法(via type(self))、内部类方法(通过cls参数)来获取此类引用。就ClassObject.__annotations__在这种情况下使用。

如果您必须有权访问函数本地主体中的注释,则需要自己解析源代码。Python AST确实保留了本地注释:

>>> import ast
>>> mod = ast.parse("def foo():\n    a: int = 0")
>>> print(ast.dump(mod.body[0], indent=4))
FunctionDef(
    name='foo',
    args=arguments(
        posonlyargs=[],
        args=[],
        kwonlyargs=[],
        kw_defaults=[],
        defaults=[]),
    body=[
        AnnAssign(
            target=Name(id='a', ctx=Store()),
            annotation=Name(id='int', ctx=Load()),
            value=Constant(value=0),
            simple=1)],
    decorator_list=[])

上面显示了带有单个注释的函数体的文本表示;该AnnAssign节点告诉我们,它a被注释为int。您可以使用以下方法收集此类注释:

import inspect
import ast

class AnnotationsCollector(ast.NodeVisitor):
    """Collects AnnAssign nodes for 'simple' annotation assignments"""

    def __init__(self):
        self.annotations = {}

    def visit_AnnAssign(self, node):
        if node.simple:
            # 'simple' == a single name, not an attribute or subscription.
            # we can therefore count on `node.target.id` to exist. This is
            # the same criteria used for module and class-level variable
            # annotations.
            self.annotations[node.target.id] = node.annotation

def function_local_annotations(func):
    """Return a mapping of name to string annotations for function locals

    Python does not retain PEP 526 "variable: annotation" variable annotations
    within a function body, as local variables do not have a lifetime beyond
    the local namespace. This function extracts the mapping from functions that
    have source code available.
 
    """
    source = inspect.getsource(func)
    mod = ast.parse(source)
    assert mod.body and isinstance(mod.body[0], (ast.FunctionDef, ast.AsyncFunctionDef))
    collector = AnnotationsCollector()
    collector.visit(mod.body[0])
    return {
        name: ast.get_source_segment(source, node)
        for name, node in collector.annotations.items()
    }

上面的 walker 查找AnnAssignment函数对象的源代码中的所有注解(因此需要有可用的源文件),然后使用 AST 源代码行和列信息来提取注解源。

鉴于您的测试功能,以上产生:

>>> function_local_annotations(test)
{'a': 'int', 'b': 'str'}

类型提示没有解析,它们只是字符串,因此您仍然必须使用该typing.get_type_hints()函数将这些注释转换为类型对象。


推荐阅读