Preamble
I'm using polars's write_excel method which has a parameter column_formats which wants a ColumnFormatDict that is defined here and below
ColumnFormatDict: TypeAlias = Mapping[
# dict of colname(s) or selector(s) to format string or dict
Union[ColumnNameOrSelector, tuple[ColumnNameOrSelector, ...]],
Union[str, Mapping[str, str]],
]
ColumnNameOrSelector: TypeAlias = Union[str, SelectorType]
SelectorType: TypeAlias = "Selector"
If I use it like this:
df.write_excel(..., column_formats={"a":"#,##0;[Red]-#,##0"})
then there is no complaint from the type checker.
If I do:
column_formats = {"a":"#,##0;[Red]-#,##0"}
df.write_excel(..., column_formats=column_formats)
then it complains:
Argument of type "dict[str, str]" cannot be assigned to parameter "column_formats" of type "ColumnFormatDict | None" in function "write_excel"
Type "dict[str, str]" is not assignable to type "ColumnFormatDict | None"
"dict[str, str]" is not assignable to "Mapping[ColumnNameOrSelector | tuple[ColumnNameOrSelector, ...], str | Mapping[str, str]]"
Type parameter "_KT@Mapping" is invariant, but "str" is not the same as "ColumnNameOrSelector | tuple[ColumnNameOrSelector, ...]"
"dict[str, str]" is not assignable to "None"
where the relevant line is: Type parameter "_KT@Mapping" is invariant, but "str" is not the same as "ColumnNameOrSelector | tuple[ColumnNameOrSelector, ...]"
I discovered that if I annotate my variable as
column_formats:dict[str|cs.Selector|tuple[str|cs.Selector], str]={"a":"abc"}
then it won't complain but the parameter is meant to be flexible and its intended flexibility is seemingly making it less flexible.
Questions:
- Why, in the first case, is a
stra validColumnNameOrSelector | tuple[ColumnNameOrSelector, ...]but in the second case it isn't?
It's interesting that it is only complaining about the key and not the value portion of the Mapping, in other words I didn't have to annotate it as dict[str|cs.Selector|tuple[str|cs.Selector], str|dict[str,str]]
- Additionally, suppose I want to put in a PR to amend
ColumnNameOrSelectoris there a way to define it such that my second usage doesn't generate a type warning without an explicit annotation?
Assuming it can be done, I'm guessing there needs to be a mapping for every key possibility so maybe this (or maybe even that last tuple possibility needs to be split up to a 4th Mapping case.
ColumnFormatDict: TypeAlias = Union[
Mapping[str, Union[str, Mapping[str, str]]],
Mapping[SelectorType, Union[str, Mapping[str, str]]],
Mapping[tuple[str|SelectorType, ...], Union[str, Mapping[str, str]]],
]
- Is that guess on the right track or way off?
{"a": "..."}is inferred asMapping[ColumnNameOrSelector, Something]. If you define a separate variable, its type is inferred on the spot, and it is obviously inferred asdict[str, str]. Mapping is invariant in key type, sodict[str, V]is not assignable toMapping[SomeSuperTypeOfStr, V]for any V.ColumnNameOrSelector- it doesn't depend on the definition, unless you want to make it aColumnNameOrSelector: TypeAlias = str.column_formats: ColumnFormatDIct = {'a': '...'}(after all, that's why that type alias is public) if you want to store it in a variable, and using inline dict if you don't need to reuse that dict.