Различение однородных и гетерогенных кортежей в перегрузках функций PythonPython

Программы на Python
Anonymous
Различение однородных и гетерогенных кортежей в перегрузках функций Python

Сообщение Anonymous »

Предположим, у меня есть база интерфейса с множеством реализаций

Код: Выделить всё

from abc import ABC

class Base(ABC): ...

class A(Base): ...
class B(Base): ...
class C(Base): ...
# ...
class Z(Base): ...
Теперь я хочу определить составной класс, содержащий замороженный набор таких объектов. Существует общий интерфейс Product и две реализации, которые используют либо гетерогенный замороженный набор (

Код: Выделить всё

MixedProduct
) или однородный замороженный набор Z (

Код: Выделить всё

ZProduct
)

Код: Выделить всё

from abc import ABC, abstractmethod
from dataclasses import dataclass

class Product(ABC):
@property
@abstractmethod
def items(self) -> frozenset[Base]: ...

@dataclass(frozen=True)
class MixedProduct(Product):
items: frozenset[Base]

@dataclass(frozen=True)
class ZProduct(Product):
items: frozenset[Z]
есть фабричная функция, которая принимает произвольное количество объектов Base и возвращает правильный объект Product

Код: Выделить всё

from collections.abc import Iterable
from typing_extensions import TypeGuard

def check_all_z(items: tuple[Base, ...]) -> TypeGuard[tuple[Z, ...]]:
return all([isinstance(item, Z) for item in items])

def make_product(*items: Base) -> MixedProduct | ZProduct:
# `items` is a tuple[Base, ...]
if check_all_z(items):  # the TypeGuard tells MyPy that items: tuple[Z, ...] in this clause
return ZProduct(frozenset(items))
return MixedProduct(frozenset(items))
поэтому эта функция возвращает ZProduct, только если все входные элементы являются Z и MixedProduct в противном случае. Теперь я хотел бы сузить тип возвращаемого значения make_product, поскольку объединение не фиксирует возможные отношения типа ввода-возврата. Мне нужно что-то вроде этого

Код: Выделить всё

reveal_type(make_product(Z()))  # note: Revealed type is "ZProduct"
reveal_type(make_product(A()))  # note: Revealed type is "MixedProduct"
reveal_type(make_product(Z(), Z()))  # note: Revealed type is "ZProduct"
reveal_type(make_product(B(), A()))  # note: Revealed type is "MixedProduct"
reveal_type(make_product(B(), Z()))  # note: Revealed type is "MixedProduct" # also contains one Z!!
Я определяю две перегрузки

Код: Выделить всё

from typing import overload

@overload
def make_product(*items: Base) -> MixedProduct: ...

@overload
def make_product(*items: Z) -> ZProduct: ...

def make_product(*items):
if check_all_z(
items
):  # the TypeGuard tells MyPy that items: tuple[Z, ...] in this clause
return ZProduct(frozenset(items))
return MixedProduct(frozenset(items))
поэтому первая перегрузка — это «объединить все», а вторая — это специализация для единственного случая, когда вы можете получить ZProduct. Но теперь MyPy жалуется на

Код: Выделить всё

error: Overloaded function signature 2 will never be matched: signature 1's parameter type(s) are the same or broader  [misc]
Итак, мой вопрос: есть ли способ просто специализировать аннотации для make_product для этого конкретного случая, который бы возвращал ZProduct каким-либо другим способом? С перегрузкой это возможно только в том случае, если все задействованные типы вообще не перекрываются. Это означало бы, что мне придется определить объединение всех других реализаций Base кроме Z и использовать его в качестве входных данных для варианта MixedProduct. Но это также не работает, потому что вы можете использовать Z во входных элементах для варианта MixedProduct, но не во всех из них (см. последний пример show_type выше). FWIW, использующий объединение всех реализаций Base (включая Z) для варианта MixedProduct, вызывает ту же ошибку MyPy.
Как еще бы я мог уметь различать однородные и гетерогенные кортежи с помощью аннотаций типов, чтобы зафиксировать правильные отношения типа ввода-возврата в моем случае?
Чтобы внести ясность: фактический код времени выполнения делает то, что я намереваюсь, я просто не могу правильно подобрать аннотации типов.

Подробнее здесь: https://stackoverflow.com/questions/788 ... -overloads

Вернуться в «Python»