如何用Mypy为Python中「非类的可调用对象」做类型注解?
问题:用类型注解限制参数为普通函数而非类
我想给以下Python函数添加精准的类型注解,缩小Callable[..., Any]的范围,让它拒绝接收类作为参数:
from typing import Callable, Any import inspect def foo(func: Callable[..., Any]) -> None: if inspect.isclass(func): raise ValueError # 执行其他操作... class Bad: pass def good() -> Bad: return Bad() foo(good) # 合法 foo(Bad) # 非法
目前运行时可通过inspect.isclass判断,但找不到对应的静态类型注解方式,希望在静态检查阶段(如Mypy)就能阻止传入类的行为。
解决方案:自定义Protocol实现静态区分
Python标准库的typing.Callable无法区分普通函数与类(因为类本身属于可调用对象),要实现静态检查层面的限制,可通过typing.Protocol自定义专属类型:
方法1:基于Protocol定义普通函数类型
from typing import Protocol, Callable, Any, runtime_checkable import inspect @runtime_checkable class RegularFunction(Protocol): __call__: Callable[..., Any] # 利用普通函数特有属性强化区分,类的构造方法无此属性 __code__: Any def foo(func: RegularFunction) -> None: if inspect.isclass(func): raise ValueError # 执行其他操作... class Bad: pass def good() -> Bad: return Bad() foo(good) # Mypy检查通过,运行正常 foo(Bad) # Mypy触发类型错误,运行时也会抛出ValueError
方法2:结合TypeGuard实现类型窄化
如果需要更灵活的检查逻辑,可搭配TypeGuard实现静态类型推断:
from typing import TypeGuard, Callable, Any import inspect def is_regular_function(func: Any) -> TypeGuard[Callable[..., Any]]: return not inspect.isclass(func) and callable(func) def foo(func: Any) -> None: if not is_regular_function(func): raise ValueError # 执行其他操作... class Bad: pass def good() -> Bad: return Bad() foo(good) # Mypy检查通过 foo(Bad) # Mypy报错:Argument 1 to "foo" has incompatible type "Type[Bad]"; expected "Callable[..., Any]"
通过is_regular_function的类型守卫,Mypy会在静态检查阶段识别出传入类的错误。
关键说明
- Python类型系统中,类本身属于可调用对象,因此
Callable会默认包含类,无法直接排除。 - 自定义
Protocol是当前最可靠的静态检查方案,需依赖Mypy、Pyright等支持Protocol的类型检查器。 - 运行时检查必须保留,静态检查无法覆盖所有动态生成的对象或边缘场景。
内容的提问来源于stack exchange,提问作者Delgan
相关产品推荐
相关产品推荐

