Python类定义实例变量类型时遇属性类型表达式错误的技术问询
问题背景
你需要为BazWrapper类的实例变量添加类型提示,以便为返回该类型的Foo函数做注解。BazWrapper是一个包装类,代码如下:
class BazWrapper: def __init__(self, number, bar): bz = bar.get_baz(number) self.title = bz.title self.location = bz.location self.is_bazable = bz.bazable
你尝试直接用baz.title作为类型提示时,收到Expected Type Expression but received "property"错误;用title: type(baz.title)时,MyPy提示type() call should not be used in type annotation Use Type[T] instead;改用title: Type[baz.title]后又回到原错误。你不想硬编码类型(比如title: str),担心Baz类属性类型变更后注解失效,想知道是否可以用属性的类型做提示,以及最佳方案是什么。
核心结论
直接用baz.title这类属性实例值作为类型提示是不可行的——类型注解要求的是静态可分析的类型表达式,而属性值是运行时的实例,不是类型本身。type(baz.title)是运行时计算的结果,MyPy无法静态解析,因此也不允许在注解中使用。
最佳解决方案
1. 类型别名集中管理(推荐)
虽然是"硬编码",但用类型别名统一管理Baz属性的类型,后续如果Baz的类型变更,只需修改别名定义,不用逐个修改注解:
from typing import Any, Optional # 定义与Baz属性对应的类型别名 BazTitleType = str BazLocationType = str BazBazableType = bool class BazWrapper: title: BazTitleType location: BazLocationType is_bazable: BazBazableType def __init__(self, number: Any, bar: Any): bz = bar.get_baz(number) self.title = bz.title self.location = bz.location self.is_bazable = bz.bazable
比如如果Baz.title后来变成可选字符串,只需把BazTitleType改成Optional[str],所有用到该别名的地方都会同步更新。
2. 运行时动态获取类型(牺牲静态检查)
如果必须动态同步Baz的类型,可以在运行时获取Baz属性的类型,再赋值给BazWrapper的类变量注解。但这种方法MyPy无法进行静态类型检查,适合对动态同步要求极高的场景:
from typing import get_type_hints, Any # 假设能获取到Baz类的类型提示(比如Bar.get_baz有返回类型注解) # 如果Bar.get_baz没有注解,也可以通过实例获取:bz = bar.get_baz(0); baz_type = type(bz) baz_type = bar.get_baz.__annotations__['return'] baz_type_hints = get_type_hints(baz_type) class BazWrapper: title: baz_type_hints['title'] location: baz_type_hints['location'] is_bazable: baz_type_hints['bazable'] def __init__(self, number: Any, bar: Any): bz = bar.get_baz(number) self.title = bz.title self.location = bz.location self.is_bazable = bz.bazable
注意:这种写法在MyPy中会报"Invalid type comment or annotation",因为baz_type_hints['title']是运行时变量,不是静态类型表达式。
3. 用Protocol定义接口(适合多属性场景)
如果BazWrapper需要包装Baz的多个属性,可以定义一个Protocol来匹配Baz的属性结构,然后用这个Protocol来标注BazWrapper或Foo函数的返回类型:
from typing import Protocol, Any class BazLike(Protocol): title: str location: str is_bazable: bool class BazWrapper: def __init__(self, number: Any, bar: Any): bz = bar.get_baz(number) self.title = bz.title self.location = bz.location self.is_bazable = bz.bazable # Foo函数可以标注返回BazLike或BazWrapper def Foo() -> BazWrapper: # 实现逻辑 ...
这种方式的好处是,只要BazWrapper的属性结构和BazLike一致,MyPy就会认可;后续Baz类型变更时,只需更新BazLike的定义即可。
内容的提问来源于stack exchange,提问作者Alexandria Leal

