输入提示此类子类工厂(如以下流行模式中的那些)的正确方法是什么?里氏替换原则原本只适用于实例,不适用于类,所以我不明白为什么mypy似乎限制子类中类方法的类型。
from typing import Any, Self
class A:
def __init__(self, a: int):
self.a = a
@classmethod
def create(cls, a: int, **keywords: Any) -> Self:
return cls(a)
class B(A):
def __init__(self, a: int, b: int):
super().__init__(a)
self.b = b
@classmethod
def create(cls, a: int, b: int, **keywords: Any) -> Self:
return cls(a, b)
运行 mypy (1.8.0) 给出:
test_factory.py:19: error: Signature of "create" incompatible with supertype "A" [override]
test_factory.py:19: note: Superclass:
test_factory.py:19: note: @classmethod
test_factory.py:19: note: def create(cls, a: int, **keywords: Any) -> B
test_factory.py:19: note: Subclass:
test_factory.py:19: note: @classmethod
test_factory.py:19: note: def create(cls, a: int, b: int, **keywords: Any) -> B
Found 1 error in 1 file (checked 1 source file)
里氏替换原则原本只适用于实例,不适用于类,所以我不明白为什么mypy似乎限制子类中类方法的类型。
仅当实例无法访问
@classmethod
时,这才是正确的。出于实用原因,唯一允许违反 LSP 的是 __init__
/ __new__
,即使它们可以在实例上隐式访问(通过 type(self)(<...>)
)。其他任何东西,甚至是“Pythonic” @classmethod
装饰的替代构造函数方法(如您这里的方法)都不允许违反它。
要解决此问题,您可以在基类 (
mypy Playground) 中宽松地键入
@classmethod
:
from __future__ import annotations
import typing_extensions as t
if t.TYPE_CHECKING:
import collections.abc as cx
class A:
def __init__(self, a: int) -> None: ...
# A positional `int` followed by any number of positional and keyword arguments
create: t.ClassVar[cx.Callable[t.Concatenate[int, ...], A]] = classmethod(lambda cls, a, **keywords: cls(a)) # type: ignore[assignment]
class B(A):
def __init__(self, a: int, b: int) -> None: ...
@classmethod
def create(cls, a: int, b: int, **keywords: t.Any) -> t.Self: ... # OK
class C(A):
def __init__(self, a: str, b: int) -> None: ...
@classmethod
def create(cls, a: str, b: int, **keywords: t.Any) -> t.Self: ... # mypy: Incompatible types in assignment