怎么通过__annotations__获取Python函数参数类型信息

晚来风
晚来风
发布时间:2026-10-30 | 11人浏览过
原创

详细讲解如何通过Python内置的__annotations__属性获取函数的参数类型信息,包含基础用法、过滤返回值、延迟求值处理及与标准库推荐方案的对比。

在 Python 3 中,函数参数类型注解(Type Hints)不仅能辅助编辑器做代码补全和静态类型检查,还会作为元数据直接绑定在函数对象上。最直接、轻量获取这些类型信息的方式,就是访问函数对象自带的内置字典属性 __annotations__。本文将从基本访问语法、提取纯参数信息、规避常见陷阱以及现代推荐方案四个方面,详细讲解如何准确获取函数参数的类型信息。

一、直接访问 __annotations__ 的基本方法

Python 函数在定义阶段如果包含类型注解,解释器就会自动创建一个名为 __annotations__ 的字典,并将参数名及其对应的类型对象保存在其中。如果函数声明了返回值类型,其类型信息会存放在固定的特殊键 'return' 中。

def calculate_fee(amount: float, rate: float = 0.05) -> float:
    return amount * rate

# 直接访问函数的 __annotations__ 属性
annotations = calculate_fee.__annotations__
print(annotations)
# 示例输出:
# {'amount': <class 'float'>, 'rate': <class 'float'>, 'return': <class 'float'>}

从输出可以看出,__annotations__ 是一个标准的 Python 字典。字典的键是参数名称(字符串),值则是定义函数时指定的类型对象(例如 float、int 或自定义类)。如果某个参数没有声明类型注解,该参数将不会出现在该字典中。

怎么通过__annotations__获取Python函数参数类型信息
图 1:函数定义中的形参与返回值注解映射到 __annotations__ 字典的内部结构

二、提取纯参数类型信息的规范步骤

在实际开发(如参数校验、API 自动文档生成)中,我们通常只需要“参数”的类型,而需要剥离“返回值”的类型。标准提取流程包括:安全获取属性、剥离 'return' 键、以及遍历键值对。

步骤 1:安全检查属性是否存在

虽然大部分普通函数都有 __annotations__,但一些由 C 语言编写的内置函数、第三方扩展对象或通过特殊动态代理封装的函数可能未定义该属性。使用 getattr(func, '__annotations__', None) 可以避免触发 AttributeError。

步骤 2:过滤排除 'return' 键

'return' 是 Python 专门保留用于存储函数返回类型的字典键,它不是函数的形参。我们可以通过字典推导式快速将真正的参数类型筛选出来。

from typing import Callable, Any, Dict

def get_parameter_annotations(func: Callable[..., Any]) -> Dict[str, Any]:
    """安全提取函数的纯参数类型注解"""
    raw_annotations = getattr(func, '__annotations__', {})
    if not raw_annotations:
        return {}
    
    # 剥离 'return' 键,仅保留实际参数类型
    return {
        param_name: param_type 
        for param_name, param_type in raw_annotations.items() 
        if param_name != 'return'
    }

# 验证测试
def register_user(username: str, age: int, is_active: bool = True) -> int:
    return 1001

params = get_parameter_annotations(register_user)
print(params)
# 示例输出:
# {'username': <class 'str'>, 'age': <class 'int'>, 'is_active': <class 'bool'>}
怎么通过__annotations__获取Python函数参数类型信息
图 2:从输入函数到获取纯参数类型映射的操作步骤流程图

三、容易混淆的边界情况与直接访问的局限

虽然直接读取 func.__annotations__ 语法简单直观,但在复杂或高版本 Python 环境下,往往会遇到以下三个容易混淆的问题:

1. 延迟求值导致的“字符串化”陷阱

在 Python 3.7+ 中,如果代码开头引入了 from __future__ import annotations(PEP 563),或者在将来某个版本的默认规则下,所有的类型注解都不会在定义时立即求值,而是被直接编译为纯字符串。此时直接访问 __annotations__ 得到的不是 <class 'int'>,而是 'int':

from __future__ import annotations

def calculate(x: int) -> int:
    return x * 2

print(calculate.__annotations__)
# 输出为字符串形态:
# {'x': 'int', 'return': 'int'}
# 此时 isinstance(val, calculate.__annotations__['x']) 将直接抛出 TypeError!

2. 未添加类型注解的参数直接缺席

许多初学者误以为 __annotations__ 能列出函数的所有参数。但事实上,__annotations__ 仅仅记录“有注解”的项目。例如对于 def send(target, message: str): pass,字典中只有 'message',没有 'target'。如果你的逻辑依赖参数顺序或参数总数,单凭 __annotations__ 就会遗漏非注解参数。

3. 直接修改字典可能导致全局副作用

直接通过 func.__annotations__ 获取的是该函数的原生字典引用(或特定状态的直接映射)。如果在业务代码中执行 del annotations['return'],可能会直接修改函数本身的元数据,破坏其他第三方库或工具对该函数的后续内省。

怎么通过__annotations__获取Python函数参数类型信息
图 3:直接访问 __annotations__ 与标准库进阶工具的特性与差异对比

四、更健壮的标准库替代方案

为了解决上述边界问题,Python 官方在标准库中提供了更加完备的内省工具。理解它们与 __annotations__ 的关系有助于在不同场景下做出合理选型。

1. 使用 inspect.get_annotations(Python 3.10+ 推荐)

从 Python 3.10 开始,PEP 563 正式推荐使用 inspect.get_annotations 替代直接访问 __annotations__。它不仅能够跨版本安全访问,还能通过 eval_str=True 参数自动将字符串格式的注解解析为真实类型:

import inspect

def process_order(order_id: int, tags: list[str]) -> bool:
    return True

# 自动处理并安全获取注解
annotations = inspect.get_annotations(process_order, eval_str=True)
param_types = {k: v for k, v in annotations.items() if k != 'return'}
print(param_types)
# 示例输出: {'order_id': <class 'int'>, 'tags': list[str]}

2. 使用 typing.get_type_hints 进行深度类型还原

如果需要在低版本(如 Python 3.7 - 3.9)或复杂类型(包含前向引用、字符串类型声明)下精确求值类型对象,使用 typing.get_type_hints 是最经典的做法:

import typing

class CustomNode:
    pass

def link_node(current: "CustomNode", next_id: int) -> None:
    pass

# typing.get_type_hints 会自动将字符串 "CustomNode" 解析为真正的类对象
hints = typing.get_type_hints(link_node)
print(hints['current'])  # 输出: <class '__main__.CustomNode'>

3. 使用 inspect.signature 获取参数全貌

如果业务不仅需要参数类型,还需要参数默认值、位置参数或关键字参数的顺序,建议使用 inspect.signature。inspect.Parameter.annotation 同样读取自该机制:

import inspect

def send_alert(message: str, level: int = 1, timeout=30):
    pass

sig = inspect.signature(send_alert)
for name, param in sig.parameters.items():
    ann = param.annotation if param.annotation is not inspect.Parameter.empty else "未注解"
    default_val = param.default if param.default is not inspect.Parameter.empty else "无默认值"
    print(f"参数: {name:<8} 类型: {str(ann):<15} 默认值: {default_val}")

# 示例输出:
# 参数: message  类型: <class 'str'>  默认值: 无默认值
# 参数: level    类型: <class 'int'>  默认值: 1
# 参数: timeout  类型: 未注解           默认值: 30

五、实战示例:基于参数注解的简易运行时校验器

以下示例展示了如何在实际工具中利用提取到的参数类型信息,实现一个轻量级的运行时类型检查装饰器:

import functools
import inspect

def validate_argument_types(func):
    """根据函数参数注解检查入参类型的装饰器"""
    # 提取纯参数类型映射
    annotations = inspect.get_annotations(func, eval_str=True)
    param_types = {k: v for k, v in annotations.items() if k != 'return'}

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # 绑定传入的实参到形参名称
        sig = inspect.signature(func)
        bound = sig.bind(*args, **kwargs)
        bound.apply_defaults()

        for name, value in bound.arguments.items():
            if name in param_types:
                expected_type = param_types[name]
                # 针对常规类型对象进行校验
                if isinstance(expected_type, type) and not isinstance(value, expected_type):
                    raise TypeError(
                        f"参数 '{name}' 类型错误: 期望 {expected_type.__name__}, 实际传入 {type(value).__name__}"
                    )
        return func(*args, **kwargs)

    return wrapper

# 测试应用
@validate_argument_types
def set_port(port: int, protocol: str):
    print(f"服务运行在端口 {port},协议为 {protocol}")

# 正确调用
set_port(8080, "HTTP")

# 异常调用触发校验(抛出 TypeError)
try:
    set_port("8080", "HTTP")  # 传入字符串而非整型
except TypeError as err:
    print(f"捕获校验异常: {err}")
    # 示例输出: 捕获校验异常: 参数 'port' 类型错误: 期望 int, 实际传入 str
怎么通过__annotations__获取Python函数参数类型信息
图 4:不同应用场景下获取函数参数类型及元数据的技术选型速查总结

六、核心要点总结

  • 字典存储机制:func.__annotations__ 是 Python 存放类型注解的原生字典,其中参数名为键,对应类型为值。
  • 区分返回值:'return' 键专门用来记录返回值类型,获取纯参数信息时需要通过 param_name != 'return' 进行过滤。
  • 非注解参数不会列出:没有显式写类型标注的参数不会记录在字典中;如需全量形参列表,应配合 inspect.signature。
  • 延迟求值注意:当存在 from __future__ import annotations 时,注解会存储为纯字符串。在 Python 3.10+ 中优先推荐使用 inspect.get_annotations(func, eval_str=True),低版本中可使用 typing.get_type_hints(func)。

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表