假设我有一堂这样的课:
class MyClass:
def __init__ (self, param1: Tuple[str,...], param2: bool) -> None:
self.member1 = param1
self.member2 = param2
self.member3 = 10
def gimmie(self) -> int | Tuple[str,...]:
return self.member1 if self.member2 else self.member3
有什么方法可以确保从 gimmie 返回的不是类型的
int | Tuple[str,...]
而是int
或Tuple[str,...]
?
我正在寻找一种方法来
cast
回归,而不是做一些严肃的杂技。
问题很简单,我构造了一个带有标志的对象,并且其中一个方法根据该标志返回两种类型中的一种。如果这是糟糕的设计,那么“正确”的方法是什么?
这是使用泛型解决此问题的方法:
from __future__ import annotations
from typing import overload, Literal, Generic, TypeVar, cast
T = TypeVar('T')
class myclass(Generic[T]):
member1: tuple[str, ...]
member2: bool
member3: int
@overload
def __init__(self: myclass[tuple[str, ...]], param1: tuple[str, ...], param2: Literal[True]) -> None:
...
@overload
def __init__(self: myclass[int], param1: tuple[str, ...], param2: Literal[False]) -> None:
...
def __init__(self, param1: tuple[str, ...], param2: bool) -> None:
self.member1 = param1
self.member2 = param2
self.member3 = 10
def gimmie(self) -> T:
return cast(T, self.member1 if self.member2 else self.member3)
reveal_type(myclass(('a', 'b'), True).gimmie())
# note: Revealed type is "builtins.tuple*[builtins.str]"
reveal_type(myclass(('a', 'b'), False).gimmie())
# note: Revealed type is "builtins.int*"
一些注意事项:
self
参数以赋予其不同的静态类型。通常,我们不会注释self
,所以请确保不要忘记这一点!a if b else c
,我就无法让 cast
拥有正确的类型。我确实同意 Samwise 的观点,这种类型的柔道是一种代码味道,并且可能隐藏了项目设计的问题。
这是使用子类和
@overload
ed 工厂函数来解决这个问题的一种方法:
from typing import Literal, Tuple, Union, cast, overload
class MyClass:
def __init__(self, param1: Tuple[str, ...], param2: bool) -> None:
self.member1 = param1
self.__member2 = param2
self.member3 = 10
def gimmie(self) -> Union[int, Tuple[str, ...]]:
return self.member1 if self.__member2 else self.member3
class _MySubclass1(MyClass):
def gimmie(self) -> Tuple[str, ...]:
return cast(Tuple[str, ...], MyClass.gimmie(self))
class _MySubclass2(MyClass):
def gimmie(self) -> int:
return cast(int, MyClass.gimmie(self))
@overload
def myclass(param1: Tuple[str, ...], param2: Literal[True]) -> _MySubclass1:
...
@overload
def myclass(param1: Tuple[str, ...], param2: Literal[False]) -> _MySubclass2:
...
def myclass(param1: Tuple[str, ...], param2: bool) -> MyClass:
if param2:
return _MySubclass1(param1, param2)
else:
return _MySubclass2(param1, param2)
myobj1 = myclass((), True)
myobj2 = myclass((), False)
reveal_type(myobj1.gimmie()) # Revealed type is "builtins.tuple[builtins.str]"
reveal_type(myobj2.gimmie()) # Revealed type is "builtins.int"
请注意,这是一项繁重的工作,需要仔细注意以确保
cast
与实现逻辑相匹配——我不知道您要解决的现实问题,但必须经历这个使输入正确排列的麻烦通常是数据建模方式中的“味道”。