python 中是否真的存在联合类型?

Do union types actually exist in python?

既然python是动态类型的,当然我们可以这样做:

def f(x):
    return 2 if x else "s"

但这就是 python 实际打算使用的方式吗?或者换句话说,联合类型是否存在于它们在 Racket 中的意义中?还是我们只像这样使用它们:

def f(x):
    if x:
        return "s"

我们唯一需要的 "union" 是 None?

仅当您使用静态类型语言时才需要联合类型,因为您需要声明一个对象可以 return 多种类型之一(在您的情况下是 intstr,或者在另一个示例中 strNoneType).

Python 只处理 对象 ,因此根本不需要考虑 'union types'。 Python 函数 return 它们 return 的功能,如果程序员想要 return 不同的类型以获得不同的结果,那是他们的选择。然后选择是架构选择,对 Python 解释器没有影响(因此 'benchmark' 这里没有任何区别)。

Python 3.5 确实引入了创建可选类型 hints 的标准,并且该标准包括 Union[...] and Optional[...] 注释。类型提示在运行时之外添加了可选的静态类型检查,就像 TypeScript 中的类型不是 JavaScript 运行时的一部分一样。

添加到@MartijnPieters 回答:

But is the way python was actually intended to be used?

根据参数返回不同的类型在任何语言中都不是好的做法。这使得测试、维护和扩展代码变得非常困难,并且恕我直言是一种反模式(当然有时是必要的邪恶)。结果至少应该通过通用接口关联起来。

union 被引入 C 的唯一原因是性能提升。但是在 Python 中,由于语言的动态特性,您没有这种性能提升(正如 Martijn 所注意到的)。实际上引入 union 会降低性能,因为 union 的大小始终是最大成员的大小。因此 Python 永远不会有 C-like union.

类型本身不存在,因为 Python 只是一种动态类型语言,但是,在较新的 Python 版本中,联合类型是 Type Hinting、[=12 的一个选项=]

from typing import Union,TypeVar

T = TypeVar('T')
def f(x: T) -> Union[str, None]:
    if x:
        return "x"

您可以使用它来注释您的代码,从而启用 IDE/Editor 级别的语法检查。

这里有几个选项可以处理在 Python 中需要 tagged union/sum type 的用例:

  • 枚举 + 元组

    from enum import Enum
    Token = Enum('Token', ['Number', 'Operator', 'Identifier', 'Space', 'Expression'])
    
    (Token.Number, 42)                            # int
    (Token.Operator, '+')                         # str
    (Token.Identifier, 'foo')                     # str
    (Token.Space, )                               # void
    (Token.Expression, ('lambda', 'x', 'x+x'))    # tuple[str]
    

    一个细微的变化使用专用的 SumType class 而不是元组:

    from dataclasses import dataclass
    from typing import Any
    
    @dataclass
    class SumType:
        enum: Enum
        data: Any
    
    SumType(Token.Number, 42)
    
  • isinstance

    if isinstance(data, int):
        ...
    if isinstance(data, str):
        ...
    

    或者结合上面的“枚举”想法:

    token = SumType(Token.Number, 42)
    
    if token.enum == Token.Number:
        ...
    
  • sumtypes模块

当然,这些方法都有各自的缺点。

以前的答案没有解决的一个用例是从预先存在的类型构建联合类型, isinstance() 认为预先存在的任何实例类型是联合类型的实例。

Python 通过 抽象基础 类 支持此功能。例如:

>>> import abc
>>> class IntOrString(abc.ABC): pass
... 
>>> IntOrString.register(int)
<class 'int'>
>>> IntOrString.register(str)
<class 'str'>

现在intstr可以看作是IntOrString的子类:

>>> issubclass(int, IntOrString)
True
>>> isinstance(42, IntOrString)
True
>>> isinstance("answer", IntOrString)
True

从 Python 3.10 开始,您可以对联合类型使用 | 分隔符。以 What's New In Python 3.10 为例:

def square(number: int | float) -> int | float:
    return number ** 2

# Instead of 
def square(number: Union[int, float]) -> Union[int, float]:
    return number ** 2

此外,如果您使用的是 Python 3.7+,则可以通过使用 __future__ 软件包获得该功能,但有一些限制:

from __future__ import annotations

# Works in Python 3.7+
def square(number: int | float) -> int | float:
    return number ** 2

# Works only in Python 3.10+
isinstance(3.10, int | float)
numeric = int | float

有关详细信息,请参阅 Union Types documentation and PEP 604