python-3.x 如何注解一个数据类,使其具有基于初始化值的返回值?

h6my8fg2  于 2023-03-09  发布在  Python
关注(0)|答案(1)|浏览(109)

我有以下(精简)数据类:

from dataclasses import dataclass
from typing import Union, Type

class BaseType: ...
class PathType(BaseType): ...
class DataType(BaseType): ...

_FinalTypes = Union[PathType, DataType]

@dataclass
class InterfaceInfo:
    what: Type[_FinalTypes]
    name: str

    def __call__(self, *args, **kwargs) -> _FinalTypes:
        return self.what(*args, **kwargs)

print(InterfaceInfo(PathType, "path"))
print(InterfaceInfo(DataType, "path"))

但是我不确定如何正确地注解它,我的目的实际上是无论您传入__init__-方法的类型是什么,它都应该作为一个物化对象从__call__中出来。
因为我现在所写的,类型检查器会认为有可能用PathType构造一个InterfaceInfo,并从中产生一个DataType对象。
如果这是一个方法,我可以使用@overload来输入提示它,但是这是一个类,所以我很困惑......我已经研究了绑定到BaseType的TypeVar。但是这不可能吗?或者类型检查器是否足够聪明,知道一个Type[PathType]进入,一个PathType需要出来?
我们该怎么解决这个问题呢?
谢谢!

mznpcxlj

mznpcxlj1#

在某种程度上,可以通过将InterfaceInfo定义为what类型的generic来解决这个问题,所以我认为您使用类型变量的想法是正确的。
假设以下具体的BaseType子类型:

from dataclasses import dataclass

@dataclass
class BaseType:
    pass

@dataclass
class PathType(BaseType):
    path: str

@dataclass
class DataType(BaseType):
    data: bytes

我们用BaseType的上限定义类型变量,然后用该类型变量注解what以及__call__返回,如下所示:

# ... import BaseType, PathType, DataType

from typing import Any, Generic, TypeVar

_T = TypeVar("_T", bound=BaseType)

@dataclass
class InterfaceInfo(Generic[_T]):
    what: type[_T]
    name: str

    def __call__(self, *args: Any, **kwargs: Any) -> _T:
        return self.what(*args, **kwargs)

用法:

path_interface = InterfaceInfo(PathType, "foo")
data_interface = InterfaceInfo(DataType, "bar")

p = path_interface(path="spam/eggs")
d = data_interface(data=b"ff00")

print(p)
print(d)

# for mypy:
reveal_type(p)
reveal_type(d)

print调用输出:

PathType(path='spam/eggs')
DataType(data=b'ff00')

mypy输出:

note: Revealed type is "PathType"
note: Revealed type is "DataType"

我们想要的和期待的。
我之所以在开头说“在某种程度上”,是因为__call__方法的签名显然只是一个廉价的解决方案,静态类型检查器无法验证我们在调用path_interfacedata_interface时传递的参数是否与what的类型兼容。
我想了一会儿,但没有找到一个类型安全的解决方案,允许将__call__注解为泛型到 that 的程度。这意味着返回类型可能是安全的,但构造函数参数中没有安全性。也许其他人知道一种方法,或者Python类型系统不适合这样做。

相关问题