thi*_*ndy 5 python iterable-unpacking python-typing pyright
想象一下我有一个对象,它是一个类的实例,如下所示:
@dataclass
class Foo:
bar: int
baz: str
Run Code Online (Sandbox Code Playgroud)
我使用它是dataclasses为了方便,但在这个问题的上下文中,不要求该类是dataclass.
通常,如果我想解压此类对象的属性,我必须实现__iter__,例如如下:
class Foo:
...
def __iter__(self) -> Iterator[Any]:
return iter(dataclasses.astuple(self))
bar, baz = Foo(1, "qux")
Run Code Online (Sandbox Code Playgroud)
然而,从像Pyright这样的静态类型检查器的角度来看,我现在丢失了bar和的任何类型信息baz,它只能推断出它们的类型Any。我可以通过iter手动创建元组参数来稍微改进:
def __iter__(self) -> Iterator[Union[str, int]]:
return iter((self.bar, self.baz))
Run Code Online (Sandbox Code Playgroud)
但我仍然没有bar和的特定类型baz。我可以注释bar然后直接baz使用,dataclasses.astuple如下:
bar: str
baz: int
bar, baz = dataclasses.astuple(Foo(1, "qux"))
Run Code Online (Sandbox Code Playgroud)
但这需要可读性较差的多级列表理解,例如
bars: list[int] = [
bar for bar, _ in [dataclasses.astuple(foo) for foo in [(Foo(1, "qux"))]]
]
Run Code Online (Sandbox Code Playgroud)
也将我联系到dataclasses。
显然,这一切都不是不可克服的。如果我想使用类型检查器,我可以不使用解包语法,但我真的很希望有一种干净的方法来做到这一点。
如果当前不可能采用通用方法,则特定于dataclasses或更好的是 的答案是可以接受的。attrs
正如 juanpa.arrivilillaga 所指出的,赋值语句文档表明,在赋值语句的左侧是一个或多个目标的逗号分隔列表的情况下,
\n\n\n该对象必须是一个可迭代对象,其项目数与目标列表中的目标数量相同,并且项目从左到右分配给相应的目标。
\n
因此,如果想要解压一个裸对象,则必须实现__iter__,当它包含多个属性类型时,它的返回类型始终为Iterator[Union[...]]or Iterator[SufficientlyGenericSubsumingType]。因此,静态类型检查器无法有效地推断解包变量的特定类型。
据推测,当 atuple位于赋值的右侧时,即使语言规范指示它将被视为可迭代,静态类型检查器仍然可以有效地推理其组成部分的类型。
因此,正如 juanpa.arrivillaga 还指出的那样,如果必须解压属性,astuple则发出tuple[...]类型的定制方法可能是最好的方法,即使它不能避免问题中提到的多级列表理解的陷阱。就问题而言,我们现在可以有:
@dataclass\nclass Foo:\n bar: int\n baz: str\n\n def astuple(self) -> tuple[int, str]:\n return self.bar, self.baz\n\n\nbar, baz = Foo(1, "qux").astuple()\nbars = [bar for bar, _ in [foo.astuple() for foo in [(Foo(1, "qux"))]]]\nRun Code Online (Sandbox Code Playgroud)\n没有任何显式的目标注释,只要我们愿意编写额外的类样板即可。
\ndataclasses\ 和attrs\ 的函数返回的结果都不astuple比 更好tuple[Any, ...],因此如果我们选择使用这些目标,则仍然必须单独注释目标。
然而,对于列表理解,这些比
\nbars = [foo.bar for foo in [Foo(1, "qux")]]\nRun Code Online (Sandbox Code Playgroud)\n?在大多数情况下可能不会。
\n最后一点,attrs 为什么不呢?页面提到“为什么不命名元组?”
\n\n\n由于它们是元组的子类,因此命名元组具有长度并且可迭代和可索引。\xe2\x80\x99 不是您对课程的期望,并且可能会掩盖微妙的拼写错误。
\n
\n\n可迭代性还意味着\xe2\x80\x99很容易意外解压命名元组,从而导致难以发现的错误。
\n
我不确定我是否完全同意这两点,但对于其他想要走这条路的人来说,这是值得考虑的。
\n