例如:
def test():
a: int
b: str
print(__annotations__)
test()
此函数调用会引发
NameError: name '__annotations__' is not defined
错误。
我想要的是在函数中获取类型注释
test
,就像全局范围或类范围中注释的返回字典一样。
有什么方法可以实现这一点吗?
如果不可能,为什么会存在这种语法?
在函数内,局部变量的注释不会被保留,因此无法在函数内访问。只有模块和类级别的变量注释才会导致附加__annotations__
对象。
[...]实际模块级注释此外,在模块或类级别,如果被注释的项是一个简单的名称,那么它和注释将存储在该模块或类的
__annotations__
属性中[.]
仅当定义了
时才设置
__annotations__
全局; 数据模型表明它是可选的:
模块[...]
预定义(可写)属性:[...];__annotations__
(可选)是一个字典,包含模块体执行期间收集的变量注释;[...]. 定义后,您可以从模块内的函数或通过
globals()
函数
访问它。如果您在
class
语句内的函数中尝试此操作,那么就知道类主体命名空间
不属于嵌套函数范围的一部分: 类块中定义的名称范围仅限于该类块;它不会扩展到方法的代码块 - 这包括推导式和生成器表达式,因为它们是使用函数作用域实现的。
您可以通过对类的引用来访问类名称空间。您可以通过使用类全局名称或内部绑定方法(通过
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()
函数
将这些注释转换为类型对象。
import re
def fn(q: int):
a: int = 1
def get_types(fn):
source = inspect.getsource(fn)
var_tps = re.findall(" +([a-z0-9]+) *?: *([a-z0-9]+) *=", source)
return var_tps
get_types(fn) # [('a', 'int')]
import inspect
import re
def get_types_annotations(fn):
source = inspect.getsource(fn)
print(source)
var_tps = {
k: v for k, v in re.findall(r"(\w+) ?: ?([\w\"\[\], ']+[^ ]) ?=", source)
}
return var_tps