Typeguard运行时类型检查中Callable参数匹配错误解决问询
Typeguard无法正确识别Callable的3个参数类型
背景
带类型标注的get_next_actions函数会返回一个带类型标注的函数actions_0(或None)。actions_0函数接收3个参数,返回一个Dict。
from typing import TYPE_CHECKING, Callable, Dict, List if TYPE_CHECKING: from some_imports import Script else: Script = object # pylint: disable=W0613 @typechecked def get_next_actions( required_objects: List[Dict[str, str]], optional_objects: List[Dict[str, str]], script: Script, ) -> Union[Callable[[AutomatorDevice, Screen, Script], Dict], None]: """根据必填和可选对象决定下一步操作 示例操作序列: 0. 选择文本框 1. 向文本框输入数据 2. 点击“下一步”按钮 应用跳转到下一页后等待预设时长,可选择重试预设次数 """ # 初始页面直接点击确定 return actions_0 return Screen( get_next_actions=get_next_actions, is_start=True, max_retries=max_retries, screen_nr=screen_nr, wait_time_sec=wait_time_sec, required_objects=required_objects, ) # pylint: disable=W0613 @typechecked def actions_0(dev: AutomatorDevice, screen: Screen, script: Script) -> Dict: """执行当前页面的第1组操作 点击“连接请求”弹窗中的“确定”按钮 """ # 点击确定按钮 dev(resourceId="android:id/button1").click() # 返回预期跳转的页面编号 action_nr: int = int(inspect.stack()[0][3][8:]) screen_nr: int = screen.screen_nr script_flow: nx.DiGraph = script.script_graph return { "expected_screens": get_expected_screen_nrs( G=script_flow, screen_nr=screen_nr, action_nr=action_nr ) }
错误信息
使用Typeguard做类型检查时,会报错不允许为Callable指定3个输入类型:
TypeCheckError: the return value (function) did not match any element in the union: Callable[list, Dict]: has too many arguments in its declaration; expected 2 but 3 argument(s) declared NoneType: is not an instance of NoneType
将返回类型标注从:
) -> Union[Callable[[AutomatorDevice, Screen, Script], Dict], None]:
修改为:
) -> Union[Callable[[AutomatorDevice, float, Script], Dict], None]:
错误会消失。
问题
如何让Typeguard正确识别传入Callable的是action_0函数的类型标注?
最小可复现示例(MWE)
mwe.py
"""最小复现示例""" import inspect from typing import TYPE_CHECKING, Callable, Dict, List import networkx as nx from typeguard import typechecked from uiautomator import AutomatorDevice from some_imports import Screen if TYPE_CHECKING: from some_imports import Script else: Script = object # pylint: disable=W0613 @typechecked def get_next_actions( required_objects: List[Dict[str, str]], optional_objects: List[Dict[str, str]], script: float, ) -> Callable[[AutomatorDevice, Screen, Script], Dict]: """根据必填和可选对象决定下一步操作 示例操作序列: 0. 选择文本框 1. 向文本框输入数据 2. 点击“下一步”按钮 应用跳转到下一页后等待预设时长,可选择重试预设次数 """ # 初始页面直接点击确定 return actions_0 # pylint: disable=W0613 @typechecked def actions_0(dev: AutomatorDevice, screen: Screen, script: Script) -> Dict: """执行当前页面的第1组操作 点击“连接请求”弹窗中的“确定”按钮 """ # 点击确定按钮 dev(resourceId="android:id/button1").click() # 返回预期跳转的页面编号 action_nr: int = int(inspect.stack()[0][3][8:]) screen_nr: int = screen.screen_nr script_flow: nx.DiGraph = nx.DiGraph() return { "expected_screens": get_expected_screen_nrs( G=script_flow, screen_nr=screen_nr, action_nr=action_nr ) } @typechecked def get_expected_screen_nrs( G: nx.DiGraph, screen_nr: int, action_nr: int ) -> List[int]: """返回当前页面执行指定操作后预期跳转的页面编号列表""" expected_screens: List[int] = [] for edge in G.edges: if edge[0] == screen_nr: if action_nr in G[edge[0]][edge[1]]["actions"]: expected_screens.append(edge[1]) return expected_screens required_objects: List[Dict[str, str]] = [{"key": "val_1"}] optional_objects: List[Dict[str, str]] = [{"key": "val_2"}] script: float = 9.4 something = get_next_actions( required_objects=required_objects, optional_objects=optional_objects, script=script, ) print(something)
some_imports.py
from typing import Callable, Dict, List, Union from typeguard import typechecked class Screen: """代表Android应用的一个页面""" # pylint: disable=R0913 # pylint: disable=W0102 @typechecked def __init__( self, is_start: bool, get_next_actions: Callable[ [Dict[str, str], Dict[str, str], Dict[str, str]], Union[Callable, None], ], max_retries: int, required_objects: List[Dict[str, str]], screen_nr: int, wait_time_sec: float, optional_objects: List[Dict[str, str]] = [], ) -> None: print("hello world.") # pylint: disable=R0902 class Script: """实验管理器 先准备实验运行环境,再调用私有方法执行包含4个阶段的实验 """ # pylint: disable=R0903 # pylint: disable=R0913 @typechecked def __init__( self, app_name: str, overwrite: bool, package_name: str, version: str, cli_input_data: Dict[str, Union[str, Dict[str, str]]], ) -> None: self.app_name: str = app_name
运行python mwe.py会抛出以下错误:
Traceback (most recent call last): File "/home/name/git/temp/mwe.py", line 78, in <module> something = get_next_actions( File "/home/name/git/temp/mwe.py", line 36, in get_next_actions return actions_0 File "/home/name/miniconda/envs/snncompare/lib/python3.10/site-packages/typeguard/_functions.py", line 164, in check_return_type check_type_internal(retval, annotation, memo) File "/home/name/miniconda/envs/snncompare/lib/python3.10/site-packages/typeguard/_checkers.py", line 756, in check_type_internal checker(value, origin_type, args, memo) File "/home/name/miniconda/envs/snncompare/lib/python3.10/site-packages/typeguard/_checkers.py", line 193, in check_callable raise TypeCheckError( typeguard.TypeCheckError: the return value (function) has too many arguments in its declaration; expected 2 but 3 argument(s) declared
解决方案
问题根源在于**Screen类型的导入逻辑不一致**:Script仅在类型检查阶段导入,运行时用object替代,但Screen是直接导入的。Typeguard在运行时检查时,会尝试解析Screen的实际类型,导致错误识别Callable的参数数量。
修复步骤
- 将
Screen的导入也放到TYPE_CHECKING分支,运行时用object替代:
if TYPE_CHECKING: from some_imports import Script, Screen else: Script = object Screen = object
- 确保所有用于类型标注的自定义类,在运行时都被替换为
object,避免Typeguard在运行时解析类型细节。
替代方案
如果需要运行时保留类型信息:
- 使用
typing.TypeVar定义Screen和Script的类型变量,确保Typeguard能正确识别。 - 升级Typeguard到最新版本,旧版本存在Callable多参数检查的bug。
修改后重新运行代码,Typeguard将不再抛出参数数量不匹配的错误。
内容的提问来源于stack exchange,提问作者a.t.
相关产品推荐
相关产品推荐

