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
方法定义了返回类型,所以当我编码时,我可以知道返回类型是什么,并据此进行操作。
现在想象一下,我创建了我的 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)
|
------------------------------------------------------------------------
解决方案
我想我找到了解决问题的方法。首先我们创建一个类型:
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 类正在适当地检索返回类型。没有必要更换任何东西。这是因为协变类型允许在预期后者的地方预期已定义边界的子类型。
推荐阅读
- python-3.x - 硒 Python 问题
- android - 结合同步和异步 observables
- node.js - 角度中node.js的用途是什么
- bash - Replace substring only in lines that match a pattern
- sql - Only update values that need updating
- data-structures - graph data structure with complex edge data
- symfony - Symfony HTTP Cache-Control 标头覆盖
- cocoa - Encrypt NSDocument on macOS
- php - 如何使用php查询mysql并将“5/9/2018 10:15:19”转换为“2018-05-09 10:15:19”
- python - Pyomo 没有收到来自求解器的解决方案