python – 在子类化时允许嵌套返回类型中的协方差

假设我们有以下示例(我花了一段时间才想到我的问题的一个最小的例子,抱歉,如果现实生活背景不是最好的,但我认为我会比使用像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方法定义了返回类型,所以当我编码时,我可以知道返回类型是什么,并根据它进行操作.

现在想象一下,我创建了我的产品和我的商店的子类(这里它没有用,但当然它可能是非常必要的).

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)
                             |
------------------------------------------------------------------------

最佳答案 我想我已经找到了解决问题的方法.首先我们创建一个类型:

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

(名称可能更好,也许是类型的数据结构.产品).

现在我们实现它:

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 iniherting是好的,所以根据经验我说在注释时应该使用TypeVar实例,而不是在子类化时.

class Fruit(Product):
    color: str

最后代码才有效.类FruitStore正在适当地检索返回类型.没有必要更换任何东西.这是因为协变类型允许期望定义的边界的子类型,而后者是预期的.

点赞