Скажем, у нас есть следующий пример (мне потребовалось некоторое время, чтобы подумать о минимальном примере моей проблемы, извините, если реальный контекст жизни не самый лучший, но я подумал, что лучше, чем просто использовать такие имена, как 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)
|
------------------------------------------------------------------------