首页 > 解决方案 > 子类化时允许嵌套返回类型的协方差

问题描述

假设我们有以下示例(我花了一段时间才想到我的问题的一个最小示例,如果现实生活环境不是最好的,我很抱歉,但我认为我会比只使用和之类的名称Base更好Child

from typing import *
from dataclasses import dataclass


@dataclass
class Product:
    name: str
    price: float


class Store:
    def __init__(self, products: List[Product]):
        self.products: List[Product] = products

    def find_by_name(self, name) -> List[Product]:
        return [product for product in self.products if product.name is name]

我已经为我的find_by_name方法定义了返回类型,所以当我编码时,我可以知道返回类型是什么,并据此进行操作。

现在想象一下,我创建了我的 Product 和 Store 的子类(在这里它相当无用,但它确实很有必要,当然)。

class Fruit(Product):
    color: str

class FruitStore(Store):
    def __init__(self, fruits: List[Fruit]):
        super().__init__(products=fruits)

    def find_by_name_and_color(self, name, color) -> List[Fruit]:
        return [fruit for fruit in self.find_by_name(name) if (fruit.name is name and fruit.color is color)]
        # Expected List[Fruit], got List[Product] instead

正如注释掉的那样,PyCharm(和任何注释检查)将检测到此函数的返回类型与基于内容来自的函数的返回类型给出的内容不匹配。

出于可读性和更容易调试的目的,我尝试替换注释但没有运气:

    def find_by_name(self, name) -> List[Fruit]: return super().find_by_name(name)
        # Expected List[Fruit], got List[Product] instead

甚至替换整个函数都不够:

    def find_by_name(self, name) -> List[Fruit]:
        return [product for product in self.products if product.name is name]
        # Expected List[Fruit], got List[Product] instead

我必须替换init中的变量定义:

    def __init__(self, fruits: List[Fruit]):
        self.products: List[Fruit] = fruits

反过来,这意味着替换整个类,并使继承无用。 如何替换注释和返回类型而不必替换整个代码?

编辑:包括评论框中引入的术语(我不知道),我想我的问题会改写为:当使用具有较窄返回类型的子方法中的父类的广泛类型的方法时,如何允许逆变?

第二个想法,我认为将默认的广泛类型从父函数更改为标准的较窄类型比允许更广泛的返回类型更好。

丑图:

________________________________________________________________________
Input                        |  Inner            | Output
------------------------------------------------------------------------
                             |
(Parent.method -> broadType) |-> (Child.method2 -> narrowedType)
                             |
------------------------------------------------------------------------

标签: pythonpython-3.xpython-3.7python-typing

解决方案


我想我找到了解决问题的方法。首先我们创建一个类型:

ProductType = TypeVar('ProductType', bound=Product, covariant=True)

(名称可能会更好,也许是一个数据结构type.Product)。

现在我们实现它:

class Store:
    def __init__(self, products: List[ProductType]):
        self.products: List[ProductType] = products

    def find_by_name(self, name) -> List[ProductType]:
        return [product for product in self.products if product.name is name]

Fruit结果证明离开Product工作正常,所以根据经验,我说TypeVar应该在注释时使用实例,而不是在子类化时使用。

class Fruit(Product):
    color: str

最后,代码可以正常工作。FruitStore 类正在适当地检索返回类型。没有必要更换任何东西。这是因为协变类型允许在预期后者的地方预期已定义边界的子类型


推荐阅读