如何输入可能具有不同自定义键和/或值的提示字典?

问题描述 投票:0回答:1

在我们应用程序的早期版本中,人们只会将一些带有纯字符串的参数传递给某些函数,因为我们没有为其中一些函数提供特定的类型提示或数据类型。比如:

# Hidden function signature:
def dummy(var: str):
    pass

# Users:
dummy("cat")

但现在我们希望为这些函数签名实现自定义数据类型,同时提供向后兼容性。说这样的话:

# Signature:
def dummy(var: Union[NewDataType, Literal["cat"]])

# Backward compatibility:
dummy("cat")

# New feature:
dummy(NewDataType.cat)

对于简单的函数签名实现这一点很好,但当签名更复杂时就会出现问题。

如果 dummy 的参数是一个可以同时使用

Literal["cat"]
NewDataType
作为键的字典,如何实现?此外,如果参数是具有相同先前键类型组合的字典,但也可以将
str
int
作为值(以及四种可能的组合),如何实现这一点?所有这些都必须符合 mypy、pylint 并使用 Python 3.9(无
StrEnum
TypeAlias
)。

我尝试了许多不同的组合,如下所示:

from typing import TypedDict, Literal, Dict, Union
from enum import Enum

# For old support:
AnimalsLiteral = Literal[
    "cat",
    "dog",
    "snake",
]

# New datatypes:
class Animals(Enum):
    cat = "cat"
    dog = "dog"
    snake = "snake"

# Union of Animals Enum and Literal types for full support:
DataType = Union[Animals, AnimalsLiteral]

# option 1, which fails:
def dummy(a: Dict[DataType, str]):
    pass

# option 2, which also fails:
# def dummy(a: Union[Dict[DataType, str], Dict[Animals, str], Dict[AnimalsLiteral, str]]):
#    pass

if __name__ == "__main__":
    # Dictionary with keys as Animals Enum
    input_data1 = {
        Animals.dog: "dog",
    }
    dummy(input_data1)

    # Dictionary with keys as Literal["cat", "dog", "snake"]
    input_data2 = {
        "dog": "dog",
    }
    dummy(input_data2)

    # Dictionary with mixed keys: Animals Enum and Literal string
    input_data3 = {
        Animals.dog: "dog",
        "dog": "dog",
    }
    dummy(input_data3)

dummy(input_data1)
没问题,但是
dummy(input_data2)
给出了以下 mypy 错误,其中虚拟签名为 2:

Argument 1 to "dummy" has incompatible type "dict[str, str]"; expected "Union[dict[Union[Animals, Literal['cat', 'dog', 'snake']], str], dict[Animals, str], dict[Literal['cat', 'dog', 'snake'], str]]"Mypyarg-type
Argument 1 to "dummy" has incompatible type "dict[str, str]"; expected "Union[dict[Union[Animals, Literal['cat', 'dog', 'snake']], str], dict[Animals, str], dict[Literal['cat', 'dog', 'snake'], str]]"Mypyarg-type
(variable) input_data2: dict[str, str]

当然做类似的事情:

input_data2: DataTypes = {
    "dog": "dog",
}

可以解决这个问题,但我不能要求用户在创建数据类型时始终这样做。

另外,我尝试了使用

TypedDict
的另一种替代方法,但我仍然遇到相同类型的 mypy 错误。

最后,我希望能够创建符合 mypy 和 pylint 的字典类型提示,它可以采用自定义键类型(如示例中所示)甚至自定义值类型,或上述的组合。

python mypy python-typing
1个回答
0
投票

核心问题是:

当然做类似的事情:

input_data2: DataTypes = {
    "dog": "dog",
}

可以解决这个问题,但我不能要求用户在创建数据类型时始终这样做。

如果您不希望用户提供注释,那么他们必须将数据直接传递给函数 (

dummy({"dog": "dog"})
),以便函数的参数类型推断启动。这是因为当类型检查器推断出来自
dict
的作业中未注释名称的类型,它们不会将类型推断为文字(请参阅 mypy PlaygroundPyright Playground):

a = {"dog": "dog"}
reveal_type(a)  # dict[str, str]

我怀疑,如果类型检查器试图推断未注释的赋值上的文字键,其他用户会抱怨误报,因为他们想要

dict[str, str]
dict[str, str]
永远无法在函数中实现更严格注释的参数 (
def dummy(a: Dict[DataType, str]): ...
)。


在我看来,你有2个选择:

  1. 通过要求用户注释来实现更严格的输入(从问题中不清楚谁提供

    DataType
    定义 - 是你/库维护者还是用户)?

  2. 不要要求用户进行注释,而是制作一个允许更宽松注释的

    @typing.overload

    from typing import overload
    
    @overload
    def dummy(a: dict[DataType, str]): ...
    @overload
    def dummy(a: dict[str, str]): ...
    

    作为奖励,当 mypy 获得支持时,您可以使用 PEP 702:

    @warnings.deprecated
    来警告您的用户,如果他们的打字太松散。请参阅Pyright Playground的示例。


附加说明:在您的问题详细信息中,您提到:

所有这些都必须符合 mypy、pylint 并使用 Python 3.9(无

StrEnum
TypeAlias
)。

尚未终止生命的 Python 版本能够利用打字的最新功能。这是因为类型检查器需要理解从

typing_extensions
的导入,无论该模块在运行时是否存在。因此,只要您不需要在运行时内省注释,就可以通过以下方式在 Python 3.9 中使用
TypeAlias
和联合语法
int | str

from __future__ import annotations

var1: int | str = 1

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from typing_extensions import TypeAlias
    
    IntStrAlias: TypeAlias = int | str

# Or

IntStrAlias: TypeAlias = "int | str"

Python 3.11 的

enum.StrEnum
在 Python 3.9 中也很容易被模仿(参见文档中的注释),并且需要类型检查器来理解这一点:

from enum import Enum

class StrEnum(str, Enum):
    dog = "dog"
>>> reveal_type(StrEnum.dog)  # Literal[StrEnum.dog]
>>> print(StrEnum.dog + " barks!")
dog barks!
© www.soinside.com 2019 - 2024. All rights reserved.