类型别名和 NewType 的区别

Difference between type alias and NewType

这有什么区别:

INPUT_FORMAT_TYPE  = NewType('INPUT_FORMAT_TYPE', Tuple[str, str, str])

还有这个

INPUT_FORMAT_TYPE  = Tuple[str, str, str]

在功能上,两者都可以工作,但 IDE 喜欢 PyCharm 标记代码如下:

return cast(INPUT_FORMAT_TYPE, ("*", "*", "All"))

InputFormat(重命名以保持类型符号一致)可以是 Tuple[str, str, str] 的子类型或别名。将它作为子类型(您的第一个示例)而不是别名(您的第二个示例)对于您想要静态验证(通过 mypy 之类的东西)所有 InputFormat 的情况很有用以某种方式。例如:

def new_input_format(a: str) -> InputFormat:
    return InputFormat((a, a * 2, a * 4))

def print_input_format(input_format: InputFormat):
    print(input_format)

print_input_format(new_input_format("a")) # Statement 1
print_input_format(("a", "aa", "aaa"))    # Statement 2

如果 InputFormat 声明为别名(通过 InputFormat = Tuple[str, str, str]),两个语句都将静态验证。如果InputFormat声明为子类型(通过InputFormat = NewType('InputFormat', Tuple[str, str, str])),只有第一个语句会静态验证。

现在这不是万无一失的。第三个语句如:

print_input_format(InputFormat(("a", "aa", "aaa")))

将进行静态验证,但它会绕过我们细心的 InputFormat 创建者 new_input_format。然而,通过在这里使 InputFormat 成为子类型,我们被迫明确承认我们正在通过必须将 tuple 包装在 InputFormat 中来创建输入格式,这使得它更容易维护这种类型的代码并发现输入格式结构中的潜在错误。

NewType 优于类型别名的另一个示例:

假设您有一个数据库,我们公开了两个函数:

def read_user_id_from_session_id(session_id: str) -> Optional[str]:
    ...

def read_user(user_id: str) -> User:
    ...

打算这样称呼(图表 A):

user_id = read_user_id_by_session_id(session_id)

if user_id:
    user = read_user(user_id)

    # Do something with `user`.
else:
    print("User not found!")

忘记我们可以在这里使用连接来使这只有一个查询而不是两个查询这一事实。无论如何,我们只想允许 read_user_id_from_session_id 的 return 值用于 read_user(因为在我们的系统中,用户 ID 只能来自会话)。我们不想允许任何值,原因是它可能是一个错误。想象一下我们这样做了(图表 B):

user = read_user(session_id)

快速 reader,它可能看起来是正确的。他们可能会认为 select * from users where session_id = 正在发生。然而,这实际上是将 session_id 视为 user_id,并且根据我们当前的类型提示,尽管在运行时导致了意外行为,但它还是通过了。相反,我们可以将类型提示更改为:

UserID = NewType("UserID", str)

def read_user_id_from_session_id(session_id: str) -> Optional[UserID]:
    ...

def read_user(user_id: UserID) -> User:
    ...

上面的图表 A 仍然有效,因为数据流是正确的。但我们必须将 Exhibit B 变成

read_user(UserID(session_id))

这很快指出了将 session_id 转换为 user_id 的问题,而没有通过所需的函数。

在其他具有更好类型系统的编程语言中,这可以更进一步。你实际上可以禁止像UserID(...)这样的显式构造,只在一个地方,导致每个人都必须经过那个地方才能获得该类型的数据。在 Python 中,您可以通过在任何地方显式执行 YourNewType(...) 来绕过预期的数据流。虽然 NewType 比简单的类型别名更有用,但它使此功能有待改进。