Python 3.12 Special Attribute - 05 - __annotations__
Python 3.12 Special Attribute -__annotations____annotations__是 Python 中用于存储变量注解type hints的内置特殊属性。它是 PEP 3107函数注解和 PEP 526变量注解引入的并在 Python 3.0 中得到支持。通过__annotations__可以在运行时访问函数参数、返回值以及类/模块级变量的类型提示信息。这一属性为静态类型检查器如 mypy、Pyright、IDE 智能提示以及运行时类型验证提供了基础设施。本文将详细解析__annotations__的定义、用途、在不同对象上的表现、底层实现并通过多个示例演示如何编写和访问注解。1.__annotations__的基本概念定义__annotations__是一个字典键为变量名或参数名值为对应的类型注解通常是类型对象如int、str但也可是任意表达式。存储位置函数/方法__annotations__存储在函数对象的__annotations__属性中包含参数和返回值的注解。类类的__annotations__属性存储类体中定义的变量注解不包括方法内部的注解。模块模块的__annotations__属性存储模块级别的变量注解。特点注解本身不强制类型检查只是元数据。Python 解释器会计算注解表达式的值并在定义时将其存入__annotations__字典。注意在 Python 3.10 之前__annotations__在某些情况下可能是一个字符串from __future__ import annotations导致的延迟评估但 Python 3.12 中该行为仍然存在不过默认是立即求值。2. 用途与典型场景类型检查供第三方静态类型检查工具mypy、pyright在开发时捕获类型错误。运行时类型验证在函数内部或装饰器中根据注解验证参数类型。自动生成文档提取函数参数类型生成 API 文档。依赖注入与框架利用注解获取期望的参数类型自动注入相应依赖。数据序列化/反序列化根据注解自动将 JSON 转换为 Python 对象。__annotations__使得 Python 在保持动态特性的同时能够获得类似静态语言的类型信息极大提高了大型项目的可维护性。3. 不同对象上的__annotations__3.1 函数的__annotations__defadd(a:int,b:int)-int:returnabprint(add.__annotations__)# {a: class int, b: class int, return: class int}键return表示返回值注解。参数名作为键。3.2 类的__annotations__classPerson:name:strage:int0print(Person.__annotations__)# {name: class str, age: class int}类变量注解存储在类的__annotations__中。注意实例属性在__init__中的注解不会出现在类的__annotations__中。3.3 模块的__annotations__# module.pyx:int10y:strhelloprint(__annotations__)# 在模块内部打印# {x: class int, y: class str}模块级变量注解存储在模块的__dict__中可通过globals()[__annotations__]访问。4. 示例与逐行解析示例 1基本函数注解defgreet(name:str,age:int0)-str:Return a greeting string.returnf{name}is{age}years old.print(greet.__annotations__)# {name: class str, age: class int, return: class str}逐行解析行代码解释1定义函数greet参数name注解为strage注解为int返回值注解为str。2-3函数文档和体实际逻辑。5访问__annotations__打印字典显示参数和返回值的类型。为什么这样写为函数参数和返回值添加类型注解提高代码可读性同时可供类型检查工具使用。__annotations__使得这些信息可以在运行时被程序读取。示例 2类变量注解classRectangle:width:floatheight:floatarea:float0.0# 带有默认值的注解def__init__(self,w:float,h:float)-None:self.widthw self.heighthprint(Rectangle.__annotations__)# {width: class float, height: class float, area: class float}逐行解析行代码解释1-4类定义width、height、area被注解为float其中area有默认值。5-7__init__方法参数w和h也有注解但这些注解属于方法不在类的__annotations__中。9打印类的__annotations__只包含类变量的注解。为什么这样写类变量注解可以用于描述实例属性预期的类型尽管实际属性在实例中但注解在类上很有用。框架可以利用这些注解自动生成表单或验证逻辑。示例 3模块级注解创建一个文件annotations_module.py# annotations_module.pyversion:str1.0author:strAlicedeffunc(x:int)-int:returnx*2在另一个脚本中导入并查看模块的__annotations__importannotations_moduleprint(annotations_module.__annotations__)# {version: class str, author: class str}注意函数内部的注解不在模块的__annotations__中只在函数自身的__annotations__中。示例 4运行时类型验证装饰器利用__annotations__实现一个简单的类型检查装饰器。importfunctoolsdeftype_check(func):functools.wraps(func)defwrapper(*args,**kwargs):# 获取函数注解annfunc.__annotations__# 将位置参数与参数名绑定bound_argsinspect.signature(func).bind(*args,**kwargs)bound_args.apply_defaults()forname,valueinbound_args.arguments.items():ifnameinannandname!return:expectedann[name]ifnotisinstance(value,expected):raiseTypeError(fParameter {name} expected{expected}, got{type(value)})# 检查返回值resultfunc(*args,**kwargs)ifreturninannandnotisinstance(result,ann[return]):raiseTypeError(fReturn value expected{ann[return]}, got{type(result)})returnresultreturnwrappertype_checkdefadd(a:int,b:int)-int:returnabprint(add(2,3))# 5add(2,3)# TypeError: Parameter b expected class int, got class str逐行解析行代码解释1导入functools用于保留函数元信息。3-17装饰器type_check接收被装饰函数。4functools.wraps(func)保留原函数的__name__等。5wrapper函数内部逻辑。6ann func.__annotations__获取函数的注解字典。7-9绑定参数使用inspect.signature将实际参数与参数名匹配。10-14遍历参数检查每个参数的类型是否与注解一致不一致则抛出TypeError。15调用原函数获取结果。16-17检查返回值如果存在返回值注解则验证类型。19-21使用装饰器add函数应用了类型检查。为什么这样写展示了如何利用__annotations__在运行时实现动态类型检查增强程序的健壮性。这种技术可用于调试、断言或作为轻量级契约。示例 5延迟注解评估from __future__ import annotations在 Python 3.7 中可以使用from __future__ import annotations使注解以字符串形式存储从而避免前向引用问题。from__future__importannotationsclassNode:defconnect(self,other:Node)-None:passprint(Node.connect.__annotations__)# {other: Node, return: None}解析other: Node中的Node被存储为字符串Node而不是类对象因为此时Node类尚未完全定义。这允许在注解中引用尚未定义的类。在 Python 3.12 中from __future__ import annotations仍然有效并且是默认行为吗不默认是立即求值但__future__导入可以启用字符串化。5. 底层实现机制在 CPython 中__annotations__的实现依赖于编译器和运行时。编译阶段当解析器遇到变量或函数定义中的注解时它会生成一个字典并将注解表达式编译为值。对于函数注解被存储在PyFunctionObject的func_annotations字段中。对于类和模块注解被存储在相应的__dict__中。字节码注解的赋值发生在函数/类/模块定义结束时。例如对于函数deff(x:int)-str:...生成的字节码大致为0 LOAD_CONST 0 (code object f) 2 LOAD_CONST 1 (f) 4 MAKE_FUNCTION 0 (annotations) 6 STORE_NAME 0 (f) 8 LOAD_CONST 2 ((x, return)) 10 LOAD_CONST 3 ((class int, class str)) 12 BUILD_MAP 2 14 STORE_ATTR 1 (__annotations__)延迟评估当使用from __future__ import annotations时编译器会将注解表达式存储为字符串而不是立即求值。这样可以在注解中使用尚未定义的名称避免循环引用。6. 注意事项与陷阱可变性__annotations__字典是可变的可以动态添加、修改或删除条目。但通常不应这样做以免与静态类型工具冲突。性能大量使用注解可能会增加内存消耗和编译时间但通常可以忽略。继承子类不会自动继承父类的__annotations__但可以通过get_type_hints()函数来自typing模块合并基类的注解。与typing.get_type_hints()的区别get_type_hints(obj)会解析前向引用字符串形式的类型并返回完整的类型字典比直接访问__annotations__更强大。Python 3.10 的变化PEP 563 将注解的延迟评估推迟到了 Python 3.13实际上默认行为仍是立即求值但from __future__ import annotations可以开启字符串化。在 Python 3.12 中这个__future__导入仍被支持。7. 与其他特殊属性的关系属性关系__doc__文档字符串与注解无直接关系但两者都是元数据。__dict____annotations__是__dict__中的一个条目对于类和模块对于函数则是独立属性。__signature__函数的签名对象包含参数信息但注解是签名的一部分。8. 总结特性说明角色存储变量、参数、返回值的类型注解类型dict位置函数、类、模块内容键为名称如参数名、变量名、return值为类型对象或字符串延迟求值时访问方式obj.__annotations__可写性可读可写但一般只读底层编译器在定义时收集并存储注解信息典型用途类型检查、运行时验证、文档生成、依赖注入最佳实践使用typing.get_type_hints()解析前向引用避免动态修改配合静态检查工具掌握__annotations__是编写现代 Python 代码的重要一环。它不仅提升了代码的可读性和可维护性还为构建健壮的工具链奠定了基础。无论是类型提示还是运行时自省__annotations__都是连接静态与动态的桥梁。如果在学习过程中遇到问题欢迎在评论区留言讨论!