Предположим, у меня есть следующее:
from collections import OrderedDict
from dataclasses import dataclass
@dataclass
class HelloWorld:
x: OrderedDict[str, int]
a = OrderedDict([("a", 0), ("c", 2), ("b", 1)])
HelloWorld(a) <--- # type error here
Возникает ошибка типа:
Argument of type "OrderedDict[Literal['a', 'c', 'b'], Literal[0, 2, 1]]" cannot be assigned to parameter "x" of type "OrderedDict[str, int]" in function "__init__"
"OrderedDict[Literal['a', 'c', 'b'], Literal[0, 2, 1]]" is incompatible with "OrderedDict[str, int]"
Type parameter "_KT@OrderedDict" is invariant, but "Literal['a', 'c', 'b']" is not the same as "str"
Type parameter "_VT@OrderedDict" is invariant, but "Literal[0, 2, 1]" is not the same as "int
Как ни странно, этот очень похожий фрагмент не выдает ошибки:
from collections import OrderedDict
from dataclasses import dataclass
@dataclass
class HelloWorld:
x: OrderedDict[str, int]
HelloWorld(OrderedDict([("a", 0), ("c", 2), ("b", 1)])) # <--- no error
Используйте typing.cast:
from collections import OrderedDict
from dataclasses import dataclass
from typing import cast
@dataclass
class HelloWorld:
x: OrderedDict[str, int]
a = OrderedDict([("a", 0), ("c", 2), ("b", 1)])
HelloWorld(cast(OrderedDict[str, int], a))
cast
часто является приемлемым решением, но здесь вы можете просто аннотировать исходную переменную. Это сохранит типобезопасность вашего кода (в отличие от cast
, который одинаково хорошо может приводить str
к dict[int, int]
). Пайрайту не следовало жаловаться на следующее:
from collections import OrderedDict
from dataclasses import dataclass
@dataclass
class HelloWorld:
x: OrderedDict[str, int]
a: OrderedDict[str, int] = OrderedDict([("a", 0), ("c", 2), ("b", 1)])
HelloWorld(x=a)
А вот и детская площадка.
Эта ошибка вызвана слишком оптимистичным выводом pyright
вместе с инвариантностью OrderedDict
(как и любой MutableMapping
).
Как заметил @InSync в комментариях, в Python 3.7 и выше вы можете построить OrderedDict из литерала dict, все еще сохраняя порядок , и в этом случае вывод менее конкретен:
a = OrderedDict({"a": 0, "c": 2, "b": 1})
HelloWorld(x=a)
@InSync, спасибо! Я включил буквальное предложение dict, но едва понимаю, какое отношение оно имеет к выбору перегрузки. Это больше похоже на детали вывода внутреннего пирайта — буквальный dict внутри вызова expr выводится как dict[str, int]
, а его пары кортежей выводятся как кортежи литералов. Не могли бы вы объяснить, почему, по вашему мнению, здесь важно конкретное overload
решение?
Предполагается, что элементы кортежа относятся к типам Literal
(например, предполагается, что ("a", 0)
относится к типу tuple[Literal["a"], Literal[0]]
). Таким образом, список соответствует аргументу dict(iterable: Iterable[tuple[_KT, _VT]])
, при этом типы элементов объединены (Literal["a", "b", "c"]
как _KT
и Literal[0, 1, 2]
как _VT
; вы можете навести курсор на вызов OrderedDict
, чтобы подтвердить это). Возможно, это намеренный выбор, потому что в противном случае Пайрайт счел бы [("a", 0), ("c", 2), ("b", 1)]
типом list[tuple[str, int]]
.
Ах, ну, пирайт просто не выводит буквальные литералы dict, ни в вызовах, ни в автономном режиме (reveal_type({'a': 1})
), и вот почему ваше предложение работает. Я думаю, достаточно ясно, что вывод в исходном фрагменте основан на типах элементов кортежа, но не стесняйтесь редактировать эту часть в моем ответе или публиковать как свою собственную :)
@InSync, но я не могу контролировать порядок вставки при использовании dict в качестве структуры ввода, верно?
@bzm3r dict
заказан с версии 3.7 (3.6, если вы используете CPython), как отмечено в ответе. На самом деле, OrderedDict
сейчас сохранился просто по историческим причинам.
@InSync О да, я только что нашел это: stackoverflow.com/questions/39980323/…
Таким образом, я мог бы также использовать dict
, а не OrderedDict
, и проблемы с заказом, которые я видел, вероятно, были результатом того, что я использовал set
для хранения интересующих ключей? Но, может быть, set
сейчас тоже заказывают, как следствие того, что dict
заказали? Не уверен. Это вызывает много вопросов, но хорошего характера.
@bzm3r set
вообще не заказан, в этом легко убедиться (например, попробуйте {'a'*n for n in range(1,6)}
). set
в Python не реализован как словарь с пустыми значениями.
В этом конкретном случае, возможно, лучше написать
OrderedDict({"a": 0, "c": 2, "b": 1})
, который, как предполагается, относится к типуOrderedDict[str, int]
без явной аннотации. Примечание о перегрузке, которую выбрал Пайрайт, также будет хорошим дополнением к этому ответу.