类型别名和 NewType 之间的区别

has*_*sii 13 python typing

这有什么区别:

INPUT_FORMAT_TYPE  = NewType('INPUT_FORMAT_TYPE', Tuple[str, str, str])
Run Code Online (Sandbox Code Playgroud)

和这个

INPUT_FORMAT_TYPE  = Tuple[str, str, str]
Run Code Online (Sandbox Code Playgroud)

从功能上来说,两者都可以工作,但是像 PyCharm 这样的 IDE 标记代码如下:

return cast(INPUT_FORMAT_TYPE, ("*", "*", "All"))
Run Code Online (Sandbox Code Playgroud)

Mar*_*hac 18

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

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
Run Code Online (Sandbox Code Playgroud)

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

现在这并不是万无一失的。第三个陈述例如:

print_input_format(InputFormat(("a", "aa", "aaa")))
Run Code Online (Sandbox Code Playgroud)

将静态验证,但它绕过了我们细心的InputFormat创建者称为new_input_format. 然而,通过InputFormat在这里创建一个子类型,我们被迫明确承认我们正在通过将 包装tuple在 an 中来创建输入格式InputFormat,这使得维护这种类型的代码和发现输入格式构造中的潜在错误变得更容易。

NewType另一个比类型别名更有好处的例子:

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

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

def read_user(user_id: str) -> User:
    ...
Run Code Online (Sandbox Code Playgroud)

打算这样称呼(附件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!")
Run Code Online (Sandbox Code Playgroud)

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

user = read_user(session_id)
Run Code Online (Sandbox Code Playgroud)

对于快速阅读的人来说,它可能看起来是正确的。他们可能会认为select * from users where session_id = $1正在发生。然而,这实际上是将 asession_id视为 a 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:
    ...
Run Code Online (Sandbox Code Playgroud)

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

read_user(UserID(session_id))
Run Code Online (Sandbox Code Playgroud)

session_id它很快指出了将 a 转换为 auser_id而不经过所需函数的问题。

在其他具有更好类型系统的编程语言中,这可以更进一步。实际上,您可以禁止UserID(...)在除一个地方之外的所有地方进行显式构造,从而导致每个人都必须经过该地方才能获取该类型的数据。在 Python 中,您可以通过在任何地方显式执行操作来绕过预期的数据流YourNewType(...)。虽然NewType比简单地输入别名更有好处,但它仍然有待改进。

  • @AntonDaneyko 看看我的编辑,它是否使它更清晰?这是一个“NewType”帮助我们正确建模数据流的示例。 (2认同)