Разрешить ковариацию во вложенных типах возвращаемых данных при создании подклассов - PullRequest
0 голосов
/ 31 августа 2018

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

1 Ответ

0 голосов
/ 31 августа 2018

Я думаю, что нашел решение своей проблемы. Сначала мы создаем тип:

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 получает типы возвращаемых данных соответствующим образом. Нет необходимости что-либо заменять . Это связано с тем, что ковариантные типы позволяют ожидать подтипы определенной границы, где ожидается последняя .

...