[英]Is it possible to maintain type information when unpacking object attributes?
想象一下,我有一个对象,它是一个类的实例,如下所示:
@dataclass
class Foo:
bar: int
baz: str
为方便起见,我使用dataclasses
,但在此问题的上下文中,不要求该类为dataclass
。
通常,如果我想解包这样一个对象的属性,我必须实现__iter__
,例如如下:
class Foo:
...
def __iter__(self) -> Iterator[Any]:
return iter(dataclasses.astuple(self))
bar, baz = Foo(1, "qux")
但是,从像pyright这样的静态类型检查器的角度来看,我现在丢失了bar
和baz
任何类型信息,它只能推断出属于Any
类型。 我可以通过手动创建iter
元组参数来稍微改进:
def __iter__(self) -> Iterator[Union[str, int]]:
return iter((self.bar, self.baz))
但是我仍然没有bar
和baz
特定类型。 我可以注释bar
和baz
然后直接使用dataclasses.astuple
如下:
bar: str
baz: int
bar, baz = dataclasses.astuple(Foo(1, "qux"))
但这需要可读性较差的多级列表理解,例如
bars: list[int] = [
bar for bar, _ in [dataclasses.astuple(foo) for foo in [(Foo(1, "qux"))]]
]
并将我与dataclasses
联系起来。
显然,这一切都不是不可逾越的。 如果我想使用类型检查器,我可以不使用解包语法,但是如果有一种干净的方法来做到这一点,我真的很想。
如果当前无法使用通用方法,则可以接受特定于dataclasses
的答案,或者更好的是attrs
。
正如 juanpa.arrivillaga 指出的那样, 赋值语句文档表明,如果赋值语句的左侧是一个或多个目标的逗号分隔列表,
该对象必须是具有与目标列表中的目标相同数量的项目的可迭代对象,并且项目从左到右分配给相应的目标。
因此,如果要解包一个裸对象,则必须实现__iter__
,当它包含多个属性类型时,它将始终具有Iterator[Union[...]]
或Iterator[SufficientlyGenericSubsumingType]
类型。 因此,静态类型检查器不能有效地推断解包变量的特定类型。
据推测,当tuple
位于赋值的右侧时,即使语言规范表明它将被视为可迭代对象,静态类型检查器仍然可以有效地推断其组成部分的类型。
因此,正如 juanpa.arrivillaga 也指出的那样,如果必须解包属性,那么发出tuple[...]
类型的定制astuple
方法可能是最好的方法,即使它不能避免多级列表的陷阱问题中提到的理解。 就问题而言,我们现在可以有:
@dataclass
class Foo:
bar: int
baz: str
def astuple(self) -> tuple[int, str]:
return self.bar, self.baz
bar, baz = Foo(1, "qux").astuple()
bars = [bar for bar, _ in [foo.astuple() for foo in [(Foo(1, "qux"))]]]
没有任何明确的目标注释,只要我们愿意编写额外的类样板。
无论是dataclasses
的也不attrs
的astuple
功能比返回任何更好的tuple[Any, ...]
所以目标仍然必须分开,如果我们选择使用这些注释。
但是,对于列表理解,这些是否比
bars = [foo.bar for foo in [Foo(1, "qux")]]
? 在大多数情况下,可能不会。
最后, attrs 为什么不呢? 页面提到,关于“为什么不命名元组?”,那
由于它们是元组的子类,namedtuples 有一个长度并且既可迭代又可索引。 这不是您对课程的期望,并且可能会掩盖细微的拼写错误。
可迭代性还意味着很容易意外地解压命名元组,从而导致难以发现的错误。
我不确定我是否完全同意这些观点中的任何一点,但是对于想要走这条路线的其他人来说,需要考虑一些事情。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.