在函数内,局部变量的注释不保留,因此无法在函数内访问。只有 模块和类级别的变量注释 会导致附加 __annotations__ 对象。
来自PEP 526 specification:
注释局部变量将导致解释器将其视为局部变量,即使它从未被分配给。不会评估局部变量的注释[.]
[...]
此外,在模块或类级别,如果被注释的项目是一个简单的名称,那么它和注释将存储在该模块或类的__annotations__属性中[。]
__annotations__ 全局仅在定义了实际的模块级注释时设置; data model states it is optional:
模块
[...] 预定义(可写)属性:[...]; __annotations__(可选)是一个字典,包含在模块主体执行期间收集的变量注释; [...].
定义后,您可以从模块内的函数或通过globals() function 访问它。
如果您在 class 语句内的函数中尝试此操作,则知道类主体命名空间是 not part of the scope of nested functions:
类块中定义的名称范围仅限于类块;它没有扩展到方法的代码块——这包括理解和生成器表达式,因为它们是使用函数范围实现的。
您将改为通过对类的引用来访问类命名空间。您可以通过使用类全局名称或内部绑定方法(通过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() function 将这些注释转换为类型对象。