MyPy 不允许将泛型属性标记为最终属性



我试图创建一个类,它有一个属性,应该是一个常数。根据在代码库中使用的类,此属性可以具有不同的类型。此外,这个属性的类型在整个类的各种类型提示中使用¹,因此我决定将类转换为Generic,如下所示:

from typing import TypeVar, Generic, Final
T = TypeVar("T")
class Foo(Generic[T]):
bar: Final[T]
def __init__(self, bar: T) -> None:
self.bar = bar

然而,我抱怨说

类体中声明的最终名不能依赖于类型变量

而如果我删除Final注释,MyPy不会引发任何错误。

我在我的代码中找不到任何逻辑错误:它只是说,无论其类型如何,bar属性应该始终引用相同的对象。是我遗漏了什么,还是这是Python和/或MyPy的一些限制?

¹在这个例子中,为了使事情简单,我只展示了一种这样的用法。

注:我使用Python 3.10.8和MyPy 0.991。

你的代码是正确的每PEP591,mypy应用规则在一个错误的顺序,注释在__init__解决问题。

这里是文档和PEP591的链接。

mypy应该检查初始化项的存在,然后决定是否缺少初始化项,但它实际上没有,并且认为尽管缺少初始化项,但您定义了最终类属性。类型变量在类属性的类型中没有意义(因为类型变量绑定到实例,而不是类),所以这里mypy错误。

因此,我们需要帮助mypy正确解决Final的类型。为此,我们可以在__init__中注释属性:

from typing import TypeVar, Generic, Final
_T = TypeVar("_T")
class Foo(Generic[_T]):
def __init__(self, bar: _T):
self.bar: Final[_T] = bar

这个类型现在检查(playground)。

这似乎是mypy的一个已知限制,并且在两年前的这个特性请求中提到了放松这个限制。从缺乏关于它的讨论来看,这似乎是一个相当小众的问题,但你总是可以参与其中。(我想说,自从那个线程中的最后一个活动以来这么长时间,如果你包含一个合理的用例并解释为什么你也想要这个功能,你可能会尊重地撞它。)

mypy没有记录typing.Final周围的这个限制,所以至少这可能值得一提。

当前的解决方案似乎是省略类命名空间中的注释,只是在__init__方法内赋值期间注释实例属性,如下所示:

from typing import TypeVar, Generic, Final

T = TypeVar("T")

class Foo(Generic[T]):
def __init__(self, bar: T) -> None:
self.bar: Final[T] = bar

class SubA(Foo[T]):
def __init__(self, bar: T) -> None:
self.bar = bar

class SubB(Foo[int]):
bar = 42

这是预期的工作,mypy给我们一个错误,试图重新分配bar:

error: Cannot assign to final attribute "bar"  [misc]
error: Cannot assign to final name "bar"  [misc]

最新更新