我正在尝试定义一个通用基类,它可以像这样实例化自身的包装实例
from typing import Callable, TypeVar, Generic
T = TypeVar("T")
U = TypeVar("U")
class From(Generic[T]):
def __init__(self, val: T):
self.val = val
@classmethod
def unit(cls, val):
return cls(val)
def bind(self, func: Callable[[T], U]):
return self.unit(func(self.val))
基本上,当使用更改类型的函数在
bind
实例上调用 From
时,例如
def to_string(i: int) -> str:
return str(i)
From(1).bind(to_string) # here, self.val = "1"
类型检查器会抱怨,因为
cls
中的 From.unit
已由 T
参数化(因此将 U
传递给 From[T]
会导致 U
不可分配给 T
)
您可以通过在
From
方法中返回 unit
的参数化实例来解决这个问题,如下所示:
class From(Generic[T]):
...
@classmethod
def unit(cls, val: U) -> "From[U]":
return From[U](val)
这就像一个魅力。
但是,当我让另一个类继承
From
时,问题就出现了。我失去了一般性:
class Just(From[T]):
pass
Just(1).bind(to_string) # equals From("1"), instead of Just("1")
不接受您应该在每个继承实例上定义具有相同形式和类名的新
unit
方法的解决方案,是否有某种方法可以重新参数化原始 cls
方法中的 unit
变量的类型?
我知道如果您在
cls.__orig_bases__
方法中打印它,您可以使用 (Generic[~T])
来查找 unit
。
所以我想也许你可以做类似的事情
setattr(cls, "__orig_bases__", (Generic[U],))
但这似乎毫无意义,以防类型检查器没有发现。
理想情况下
self
上应该有一些方法允许您访问未参数化的基类,这样您就可以 self.<get_baseclass>[U](<val>)
.
现在我的解决方案是在
# type: ignore
中添加 unit
,如下所示:
@classmethod
def unit(cls, val: U) -> "From[U]":
"""Return a new instance of the same encapsulating class, wrapping `val`"""
return cls(val) # type: ignore
但这并不理想。
使用
Self
作为 unit
和 bind
的返回类型。
class From(Generic[T]):
def __init__(self, val: T):
self.val = val
@classmethod
def unit(cls, val) -> Self:
return cls(val)
def bind(self, func: Callable[[T], U]) -> Self:
return self.unit(func(self.val))
现在
reveal_type(Just(1).bind(to_string))
根据需要返回 Just[int]
。