子类化时允许嵌套返回类型的协方差

时间:2018-08-30 22:17:16

标签: python python-3.x python-3.7 python-typing

说我们有以下示例(花了我一段时间来思考我的问题的最小示例,很抱歉,如果现实生活中的环境不是最好的,但是我认为比起只使用{{ 1}}和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

如已注释掉,PyCharm(和所有注释检查)将检测到该函数的返回类型与基于内容来源的函数的返回类型所给定的内容不匹配。

出于便于阅读和调试的目的,我尝试用幸运的方式替换注释:

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

甚至不替换整个函数就足够了:

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

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

    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

反过来,这意味着替换整个类,并使继承无效。 如何在不替换整个代码的情况下替换仅 注释和返回类型?

编辑:包括注释框中引入的术语(我不知道),我想我的问题应该改述为:当使用子方法中父类的广泛类型的方法时返回类型较窄的情况下,如何考虑方差?

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

丑图:

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

1 个答案:

答案 0 :(得分:1)

我认为我发现了解决我的问题的方法。首先我们创建一个类型:

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类正在适当地获取返回类型。 无需更换任何东西。这是因为协变类型允许期望已定义边界的子类型