我们能否提供有关何时使用协变和逆变的示例和动机?

问题描述 投票:0回答:1

我有机器/深度学习背景,但我也渴望成为一名优秀的软件工程师。

我在寻找协变/逆变的真实用例时遇到了一些麻烦(部分原因是这对我来说是一个新概念,并且初始学习曲线很困难)。

我想要一个关于何时使用协变/逆变的具体动机和示例,特别是,我希望这个示例是这样的:如果不应用协变/逆变,那么应用程序将会有错误/类型不安全。

首先,我知道 PyTorch 的

Dataset
DataLoader
是由协变类型参数化的:

class Dataset(Generic[T_co]):
    r"""An abstract class representing a :class:`Dataset`.

    All datasets that represent a map from keys to data samples should subclass
    it. All subclasses should overwrite :meth:`__getitem__`, supporting fetching a
    data sample for a given key. Subclasses could also optionally overwrite
    :meth:`__len__`, which is expected to return the size of the dataset by many
    :class:`~torch.utils.data.Sampler` implementations and the default options
    of :class:`~torch.utils.data.DataLoader`. Subclasses could also
    optionally implement :meth:`__getitems__`, for speedup batched samples
    loading. This method accepts list of indices of samples of batch and returns
    list of samples.

    .. note::
      :class:`~torch.utils.data.DataLoader` by default constructs a index
      sampler that yields integral indices.  To make it work with a map-style
      dataset with non-integral indices/keys, a custom sampler must be provided.
    """

    def __getitem__(self, index) -> T_co:
        raise NotImplementedError("Subclasses of Dataset should implement __getitem__.")

    # def __getitems__(self, indices: List) -> List[T_co]:
    # Not implemented to prevent false-positives in fetcher check in
    # torch.utils.data._utils.fetch._MapDatasetFetcher

    def __add__(self, other: 'Dataset[T_co]') -> 'ConcatDataset[T_co]':
        return ConcatDataset([self, other])

    # No `def __len__(self)` default?
    # See NOTE [ Lack of Default `__len__` in Python Abstract Base Classes ]
    # in pytorch/torch/utils/data/sampler.py

我想知道是否有人能举出令人信服的例子来说明为什么

Dataset
需要协变。

python types pytorch typing liskov-substitution-principle
1个回答
0
投票

简短的答案是,您希望

DataSet[bool]
成为
DataSet[int]
的子类型,因为
bool
int
的子类型。默认情况下,泛型类型是不变,因为假设类型是可变的。

比较元组和列表。元组是协变的,因为您无法修改它们。您唯一能做的就是从它们中读取 ,因此如果您需要 tuple[int]

 值,则 
int
 的任何子类的元组都可以。另一方面,列表是不变的,因为您不一定知道您是否会从列表中读取或向列表中写入。 (如果读 
int,可以用 list[bool] 代替 list[int]
;如果写 
bool
,可以用 
list[int]
 代替 
list[bool]
。但一般来说,两者都不能替代另一个。)
逆变并没有真正出现在传统容器中,但在谈论函数类型时却出现了。如果 
f
 接受与 
g

相同的参数(但可能更多),并且返回

some

 相同的参数(但可能更少),则函数 
f
 可以替代函数 
g
 
g可以。我们将此描述为函数类型在其参数中是逆变
,在其返回类型中是
协变

© www.soinside.com 2019 - 2024. All rights reserved.