如何在 python 函数范围内获取类型注释?
How to get type annotation within python function scope?
例如:
def test():
a: int
b: str
print(__annotations__)
test()
此函数调用引发 NameError: name '__annotations__' is not defined
错误。
我想要的是在函数test
中获取类型注解,比如全局作用域或class作用域中返回的注解字典。
有什么方法可以实现吗?
如果不可能,为什么存在这种语法?
在函数中,局部变量的注解未保留,因此无法在函数中访问。只有 模块和 class 级别 的变量注解会导致附加 __annotations__
对象。
Annotating a local variable will cause the interpreter to treat it as a local, even if it was never assigned to. Annotations for local variables will not be evaluated[.]
[...]
In addition, at the module or class level, if the item being annotated is a simple name, then it and the annotation will be stored in the __annotations__
attribute of that module or class[.]
仅当定义了实际模块级注释时才设置__annotations__
全局; data model states it is optional:
Modules
[...] Predefined (writable) attributes: [...]; __annotations__
(optional) is a dictionary containing variable annotations collected during module body execution; [...].
定义后,您可以从模块内的函数或通过 globals()
function.
访问它
如果您在 class
语句内的函数中尝试此操作,则知道 class 正文命名空间是 not part of the scope of nested functions:
The scope of names defined in a class block is limited to the class block; it does not extend to the code blocks of methods – this includes comprehensions and generator expressions since they are implemented using a function scope.
您将改为通过对 class 的引用来访问 class 命名空间。您可以通过使用 class 全局名称或通过 type(self)
的内部绑定方法,通过 cls
参数在 class 方法内部获得此类引用。在这种情况下只需使用 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 将这些注释转换为类型对象。
我从别人那里复制的另一个简单的解决方案。
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')]
例如:
def test():
a: int
b: str
print(__annotations__)
test()
此函数调用引发 NameError: name '__annotations__' is not defined
错误。
我想要的是在函数test
中获取类型注解,比如全局作用域或class作用域中返回的注解字典。
有什么方法可以实现吗?
如果不可能,为什么存在这种语法?
在函数中,局部变量的注解未保留,因此无法在函数中访问。只有 模块和 class 级别 的变量注解会导致附加 __annotations__
对象。
Annotating a local variable will cause the interpreter to treat it as a local, even if it was never assigned to. Annotations for local variables will not be evaluated[.]
[...]
In addition, at the module or class level, if the item being annotated is a simple name, then it and the annotation will be stored in the
__annotations__
attribute of that module or class[.]
仅当定义了实际模块级注释时才设置__annotations__
全局; data model states it is optional:
Modules
[...] Predefined (writable) attributes: [...];__annotations__
(optional) is a dictionary containing variable annotations collected during module body execution; [...].
定义后,您可以从模块内的函数或通过 globals()
function.
如果您在 class
语句内的函数中尝试此操作,则知道 class 正文命名空间是 not part of the scope of nested functions:
The scope of names defined in a class block is limited to the class block; it does not extend to the code blocks of methods – this includes comprehensions and generator expressions since they are implemented using a function scope.
您将改为通过对 class 的引用来访问 class 命名空间。您可以通过使用 class 全局名称或通过 type(self)
的内部绑定方法,通过 cls
参数在 class 方法内部获得此类引用。在这种情况下只需使用 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 将这些注释转换为类型对象。
我从别人那里复制的另一个简单的解决方案。
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')]